Version control
Spike VCS: why every AI turn is a commit, how snapshots and branches work, what to do when a commit is rejected, how to resolve conflicts, and how to roll an app back safely.
Spikefrost apps are version-controlled by Spike VCS, which is part of the platform rather than a tool you install. The reason it exists: when an AI agent changes your app in the cloud, that has to become history automatically — otherwise you'd have no idea what changed, when, or by whom.
This is not git. There is no git repository in an app checkout. Don't run git there.
The model
- A commit is a full snapshot of the app's file tree, with a message, an author, and parents.
- Cloud AI turns and web-editor saves commit automatically. Local checkouts commit explicitly.
- Each commit carries a
manifestSha— a content hash of its entire tree. EqualmanifestShameans byte-identical trees. That's how you compare two versions without reading a single file. - Branches are pointers. Only the worktree branch (normally
main) materializes to the app's cloud worktree and deploys. - History is a first-parent walk:
sf vcs logfollows the branch's own line, and merged side branches appear as merge commits rather than interleaved entries.
The working loop
sf vcs status # local changes + whether the remote moved
sf vcs pull # three-way merge the remote head into your tree
# ...edit...
sf vcs commit -m "add lead export" # snapshot → cloud worktree updates in seconds
sf vcs commit is how you save to the app. It's atomic, attributed, and protected: if someone advanced the branch first, your commit is rejected with head_moved instead of overwriting their work. Pull, resolve, commit again.
Two habits:
- Pull before you start. Cloud agents and teammates commit to the same app.
- Commit coherent chunks — a feature landed, a bug fixed, a refactor done. Not after every file, and not once at the end of a three-hour session. Small frequent commits keep merges trivial.
Checkouts are shared and live
This surprises people, so it's worth being explicit: other sessions write into the same checkout while you work. Parallel desktop-app chats, another agent's commits and pulls, and the platform's own guidance files being refreshed on a version bump.
So if a patch fails because a file's tail changed, or your base commit moved without you pulling — that's this, not corruption. The protocol:
sf vcs statusbefore large edits- Re-read a file immediately before patching it
- Commit early and small
Reading history
sf vcs log # newest first
sf vcs log -n 50 --branch dev
sf vcs show <commit|@N> # one commit: metadata + files changed vs its parent
sf vcs diff # changed paths vs your base commit
sf vcs diff app/src/index.tsx # unified diff for one path
sf vcs refs # every branch + head, the worktree branch, the autodeploy map
@N is shorthand for "N commits back", so sf vcs show @1 is the previous commit.
sf vcs refs is the right first command for any "what state is this app in?" question — it answers which branch deploys, where every branch points, and whether autodeploy is on.
The desktop app's History button shows the same history, including every commit an agent made.
Conflicts
Conflicts materialize in the file, as markers:
<<<<<<< local
your version
=======
theirs
>>>>>>> remote
Binary files get a .remote-* sibling instead. Resolve by keeping the right lines and deleting the marker lines, then commit. Never commit unresolved markers — they compile in some file types and produce nonsense at runtime.
sf vcs pull --theirs takes the remote side wholesale when you know your local version is disposable.
Branches
For larger work you want isolated:
sf vcs checkout feature-x --create
# ...work, commit...
sf vcs checkout main
sf vcs merge feature-x
Default to main for normal work. Only mapped branches deploy.
A merge is a two-parent commit on the target branch, and there is no fast-forward. After merging dev into main, the two heads are different commit ids — permanently, and that's correct. main's head is the new merge commit; dev keeps its own.
Do not "fix" differing head ids by re-merging or re-committing. To check whether two branches actually have the same content, compare their manifestSha:
sf vcs show <main-head> --json # → commit.manifestSha
sf vcs show <dev-head> --json
Equal sha means identical trees, regardless of the commit ids. Merging with no content difference produces an empty-diff merge commit — also normal.
Rolling back
sf vcs rollback <commit|@N> # plans and shows what would change
sf vcs rollback <commit|@N> --confirm # applies it
sf vcs pull # bring your checkout in line
A rollback creates a new commit that restores the earlier tree, so it's itself reversible and history stays intact. Plan first, then confirm.
To roll back a running environment without touching the branch, deploy an older commit instead:
sf deploy --env production --commit @3
Autodeploy
sf vcs autodeploy on # commits to main deploy production
sf vcs autodeploy main=production dev=dev # map branches to environments
sf vcs refs # check the current map
With a mapped branch, every commit that advances it deploys that environment. Convenient and sharp: warn people before committing untested work to a mapped branch. Details in Environments.
Two files to know
CLAUDE.md/AGENTS.md— generated agent instructions, machine-local, never versioned. The platform rewrites them on version bumps, so hand edits are lost.DECISIONS.md— versioned app content. Your decision log: what you chose, what you rejected, why. It travels with the app and is copied on clone, so the next session inherits the reasoning rather than re-litigating it.
Next
- Environments — mapping branches to environments and promoting between them.
- Build and deploy — what happens after a commit.
Frequently asked questions
Can I use git in an app checkout?
No — there is no git repository there. Spike VCS is the version control, and it's built into the platform so that cloud AI turns and editor saves are committed automatically. Use sf vcs commands.
What does 'head_moved' mean?
Someone else advanced the branch before your commit landed, so your commit was rejected rather than silently overwriting their work. Run sf vcs pull, resolve anything that conflicts, and commit again.
After merging a branch into main, why are the two heads still different?
Because a merge creates a two-parent commit on the target branch — there's no fast-forward. Different head ids after a merge is correct and permanent. Compare content with manifestSha, not commit ids.
Is a rollback destructive?
No. A rollback creates a new commit that restores the earlier tree, so it's itself reversible. Plan first, then confirm, and pull afterwards so your checkout matches.
Why did my file change under me while I was editing?
Checkouts are shared and live. Parallel chats, cloud agents, and the platform's own guidance refresh all write to the same app. Run sf vcs status before large edits and re-read a file immediately before patching it.