ANTIGRAVITY LABJP
Articles/Antigravity Basics
Antigravity Basics/2026-08-20Intermediate

Count What Writes Outside Your Workspace Before You Upgrade to CLI 1.1.14

Antigravity CLI 1.1.14 makes paths outside the workspace read-only by default. If your source assets and delivery targets live outside the repository, it is worth knowing what breaks before you upgrade. Here is a script that inventories your write targets, and the two ways it quietly undercounts them.

Antigravity CLI27permissions4workspace8wallpaper app2operations29

Premium Article

The source images for my wallpaper apps do not live in the app repository.

Originals sit in a cloud-synced folder, derived renditions are written somewhere else, and the delivery staging area is somewhere else again. Running six apps as an indie developer, keeping images out of the app repository was simply the least painful arrangement. The repository stays small, and several apps can share the same source set.

I have been handing the sorting and rendition work to agents with that layout in place. From the agent's point of view, both the reading and the writing happen outside the workspace.

That is where I stopped while reading the Antigravity CLI 1.1.14 notes. Access to paths outside the workspace is now read-only by default. Writing requires authorisation appropriate to the execution mode.

So in my layout, reading the source would still work, and writing the output might not. And the moment I would find out is the next time that job runs.

Noticing after the upgrade is noticing too late

The awkward part of a change like this is how quietly it breaks things.

If you are sitting at the editor, an approval prompt appears and you immediately understand. The trouble is with the overnight batch and the non-interactive runs invoked from CI. I wrote about protecting the turn boundary in non-interactive runs in Guarding the Turn Boundary in Non-Interactive Runs, and the assumption underneath that design was that permitted operations go through. When the default itself shifts, that assumption is the first thing to re-check.

So I settled on one piece of work to do before upgrading: count every place in my repository that writes outside the workspace.

Just count. Deciding what to fix comes afterwards. I chose that order because fixing while counting means forgetting the ones you could not fix.

The smallest script that finds write targets

The job is simple. Walk the scripts and config files in the repository, find write operations, and report the ones whose destination points outside the tree.

My first version was about this short.

#!/usr/bin/env python3
"""Inventory writes that land outside the workspace (v1, literals only)."""
import os, re, sys
 
WRITE_PATTERNS = [
    (re.compile(r'(?:^|\s)>>?\s*("?)([^\s"\'|;&]+)\1'), "shell redirect"),
    (re.compile(r'\b(?:cp|mv|rsync)\b[^\n]*?\s("?)(/[^\s"\']+|\.\.[^\s"\']*)\1\s*$'), "copy dest"),
    (re.compile(r'open\(\s*["\']([^"\']+)["\']\s*,\s*["\'][^"\']*[wax][^"\']*["\']'), "python open(w)"),
    (re.compile(r'--out(?:put)?[= ]\s*("?)([^\s"\']+)\1'), "cli --out"),
]
SKIP_DIRS = {".git", "node_modules", ".next", "__pycache__"}
TEXT_EXT = {".sh", ".bash", ".py", ".mjs", ".js", ".ts", ".json", ".yml", ".yaml", ".toml"}
 
def audit(root):
    root = os.path.abspath(root)
    for dirpath, dirnames, filenames in os.walk(root):
        dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]
        for name in filenames:
            if os.path.splitext(name)[1] not in TEXT_EXT:
                continue
            path = os.path.join(dirpath, name)
            lines = open(path, encoding="utf-8", errors="ignore").read().splitlines()
            for lineno, line in enumerate(lines, 1):
                for pattern, kind in WRITE_PATTERNS:
                    m = pattern.search(line)
                    if not m:
                        continue
                    raw = m.group(m.lastindex).strip('"\'')
                    if raw.startswith(("$", "~", "%")):
                        print(f"UNRESOLVED\t{os.path.relpath(path, root)}:{lineno}\t{kind}\t{raw}")
                        continue
                    target = raw if os.path.isabs(raw) else os.path.normpath(os.path.join(dirpath, raw))
                    if not (target + os.sep).startswith(root + os.sep):
                        print(f"OUTSIDE\t{os.path.relpath(path, root)}:{lineno}\t{kind}\t{target}")
 
if __name__ == "__main__":
    audit(sys.argv[1] if len(sys.argv) > 1 else ".")

Narrowing by extension in TEXT_EXT keeps the walk from opening images and binaries. Leaving node_modules out of SKIP_DIRS buries the output under build scripts from dependencies.

To test it I built a small fixture that mirrors my own layout: source input outside, derived output via a relative path, delivery staging outside, a log outside, and a cache under the user directory.

# scripts/build_assets.sh
SRC="/Volumes/Shared/wallpaper-source/20260820"
OUT="../dist/derived"
cp "$SRC"/*.png ./work/
python3 scripts/derive.py --out "$OUT"
rsync -a ./work/ /Users/shared/cdn-staging/
echo "done" >> /var/log/wallpaper/build.log
# scripts/derive.py
import json
CACHE = "/Users/shared/agent-cache/derive.json"
def save(data):
    with open(CACHE, "w") as f:
        json.dump(data, f)
    with open("./local_report.txt", "w") as f:
        f.write("ok")

Those two files contain three writes that land outside the workspace: the rsync to delivery staging, the >> log append, and open(CACHE, "w").

Version one produced this.

UNRESOLVED  scripts/build_assets.sh:5  cli --out       $OUT
OUTSIDE     scripts/build_assets.sh:6  rsync dest      /Users/shared/cdn-staging
OUTSIDE     scripts/build_assets.sh:7  shell redirect  /var/log/wallpaper/build.log

summary: outside=2 unresolved=1

Two of three. The open(CACHE, "w") vanished entirely.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
You will be able to mechanically inventory every place your project writes outside its workspace, before the upgrade rather than after
You will be able to avoid the failure mode where an automated job goes read-only and you find out from a missing file the next morning
You will be able to decide, target by target, whether to widen the workspace, authorise the write, or move the output somewhere it never needed to leave
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Antigravity2026-08-17
Deleting Duplicate Rows Will Not Shrink Your Antigravity CLI Conversation Database
A schema-agnostic way to audit conversation database growth, plus measurements showing why deletion reclaims nothing and why freelist_count is the wrong number to trust when you estimate how much you can get back.
Antigravity2026-08-10
The Line I Thought Matched Nothing Was Approving Everything: Auditing Empty Allowlist Entries
Allowlist entries that decompose to zero command words matched every command and silently auto-approved it. Here is how I scanned my own config, separated the hole the fix closed from the one it did not, and rewrote matching to a prefix-token comparison.
Antigravity2026-07-27
Who Approved the Right Side of &&? Splitting Shell Commands Before Matching Allow Rules
The approval dialog showed part of what actually ran. Here is a harness that splits compound shell commands without breaking quotes or command substitution, matches allow rules per segment, and the numbers from running it over 45 real commands.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →