Deploy state
Every deploy records a map from each code resource (kind:slug) to the real
uuid Cargo assigned it, plus its outputs and a content hash:
cargo-ai project init creates the state and writes that cargo.state.json as part
of the scaffold, so the pointer is in the first commit rather than appearing in
whichever teammate deploys first. It records only uuids, hashes and outputs —
never secret values.
One state per repo, at most 10 per workspace
A state belongs to a git repo, not to a workspace. Eachproject init creates its
own and commits its own uuid, so a workspace holding five GTM repos holds five
states and they never share a map. A workspace may hold 10 live states; creating
an eleventh is refused until one is removed.
Managing it
Projects created before states moved to the workspace
Theircargo.state.json holds the resource map itself, and it keeps working:
plan, deploy and everything else read and write that file exactly as they
did. The file is the state, so losing it has no recovery path but git.
Moving one into the workspace is a single command, run once by one person:
Nothing migrates a project on its own. The pointer has to reach your teammates
through git — if a deploy moved the map for whoever ran it first, everyone
still on the old file would carry on deploying from a state that had already
moved.
stateUuid means the
workspace does. Anything that let you contradict it would only ever deploy a
second copy of everything the pointer already tracks.
Lock and sibling files
A deploy takes a lock on the state for its duration, so two runs cannot interleave writes. A lock is released when the run ends, and one left by a run that died expires after an hour;--force steals one you believe is stale.
Cargo also writes sibling files next to cargo.state.json — a
cargo.state.bak.json (local rollback snapshot), a cargo.state.audit.jsonl
(run log), a cargo.state.lock (local backend only), and a
cargo.state.cache.json (the last blob read from the workspace, so project info
can report what is deployed without a round trip). Only cargo.state.json is
committed; git-ignore the rest:
Drift
Cargo compares your code against state. It can also compare against the live workspace to catch changes made outside the project (e.g. someone edits an agent in the Cargo UI, or deletes a folder).Detect (read-only)
unchanged,
modified externally, or deleted externally. It changes nothing.
A modified resource also names the fields the edit landed in, so you can see
whether re-applying your code over it would lose anything:
modified externally with no fields until its next deploy.
Correct
- Modified externally → re-applies your code over it (code wins).
- Deleted externally → the deploy stops and asks you to re-run with
--recreate-deletedbefore it will recreate anything — a deliberate gate so a resource someone removed on purpose doesn’t silently come back.
Drift is measured against the state captured at the last deploy, not guessed
from code — so it reflects real changes to the live resource. A transient read
error is reported as
unknown, never as a deletion, so a network blip can’t
trigger a mass re-create.Code and UI round-trips
Resources deployed from code remain fully editable in the Cargo UI — but the project never reads those edits back into your files. What happens to UI work on the next deploy follows directly from the hash model:- A plain
project deploycompares code against state, not against the live workspace. If you haven’t changed a resource in code, it plans as=unchanged and is skipped — UI edits to it survive every deploy. - The moment you change that resource in code, the next deploy pushes the full code spec and overwrites the resource — including anything changed in the UI since.
project deploy --refreshre-applies code over every externally-modified resource, whether or not its code changed. Reach for it when you want to reset to what the repo says; avoid it while UI work is in flight.
cargo-ai project refresh before deploying to see exactly which resources have
diverged.
Secrets and drift
Becausesecret() values are excluded from the content hash, rotating a secret
does not show as drift and a plain deploy won’t push the new value (nothing
changed). To roll a rotated secret, re-apply the resource (make any other change,
or use --refresh). Use env() instead if you want a config value tracked in
the hash. See Secrets & environments for
the full secret() vs env() rules and how to deploy the same code to a second
workspace.
