Workspaces and git
When a task is bound to a repository, the runner keeps a real checkout of that repository on your machine and works in it. Nothing is cloned fresh per task, and nothing is thrown away between runs.
A task gets bound to a repository by the project it belongs to, so most of this begins once you have connected a repository. A task whose project has none still runs, on the terms described in when the project has no repository.
One workspace per repository
Section titled “One workspace per repository”Workspaces are long lived, one per repository, not one per task:
~/.commandchain/workspaces/ github-com-alpenglow-summitline/ .git/ ... the working tree, on the current task branchThe directory name is derived from the repository URL, so the same repository always lands in the same place no matter how it is addressed:
| Repository URL | Directory |
|---|---|
https://github.com/alpenglow/summitline.git | github-com-alpenglow-summitline |
git@github.com:alpenglow/summitline.git | github-com-alpenglow-summitline |
ssh://git@bitbucket.org/alpenglow/summitline | bitbucket-org-alpenglow-summitline |
Reusing the checkout is what keeps a run on a large repository quick: the second task on a repository fetches rather than clones.
The branch a task works on
Section titled “The branch a task works on”Each task gets its own branch, named task/<task id>. A run can override the name, and it can say which branch to start from.
The runner prepares the branch before the agent starts:
- Clone, if the repository is not in the workspace root yet.
- Fetch all remotes and prune deleted ones.
- Stash anything left uncommitted by a previous failed attempt, under a name that says where it came from, and log a warning. Work is never silently discarded.
- Check out the task branch: existing locally, it is checked out and updated; existing only on the remote, it is checked out to track that remote; new, it is created from the base branch.
Then the coding agent runs, inside the sandbox, in that working tree.
The runner pushes, not the agent
Section titled “The runner pushes, not the agent”When the agent finishes successfully, the runner commits the changes and pushes the branch, and reports the resulting commit back to your workspace. The commit is authored as CommandChain Runner <runner@commandchain.local>, so an automated change is never disguised as a person’s.
This split is a security property, not a convenience:
- The agent is sandboxed and has no access to your git credentials. It cannot push, and it cannot read the keys that would let it.
- The runner is not sandboxed, and it is the only thing that talks to your remote. It uses the SSH agent or git credential helper already configured for your user, so private repositories work with the access you already have.
- Git executes code it finds in a repository, in hook scripts and in command-valued configuration keys. Those paths are kept outside what the agent can write, and the runner re-establishes the layout after the agent process is gone and before it runs git itself. It also runs its own git with hooks disabled outright.
- The agent’s own output directory is never committed. Notes, scores, and other files an agent writes for CommandChain go into a directory of its own inside the working tree, and the runner stages every commit with that directory left out. Nothing an agent wrote for CommandChain rather than for you reaches your branch, even if the agent tried to commit it itself.
Verification commands run inside the sandbox
Section titled “Verification commands run inside the sandbox”When a task specification names a toolchain, the runner runs that toolchain’s prepare commands, the ones that install the project’s dependencies, before the agent starts. They run in the same working tree the agent will work in, inside the same sandbox, with the same run environment.
- The runner’s own credentials never reach them. A prepare command is given the run environment and nothing else. The runner’s control-plane credential, its git token, and any coding agent API key stay outside the sandbox, exactly as they do for the agent itself.
- Every command has a time limit taken from what is left of the run’s clock. A command that does not finish inside that limit is stopped and recorded as timed out, and so is a command the clock ran out on before it could start. Nothing is granted a fresh budget partway through a run.
- A prepare command that fails ends the run. The run finishes as
inconclusive, with the reasontoolchain_unavailable. That is a statement about the environment rather than a verdict on the work: nothing could be installed, so nothing could be verified.
Anything the commands print is captured for the run’s record with the run environment’s secret values masked out first, on the same terms as the agent’s own output. See the security model for what a sandboxed process can reach, and run states for how an inconclusive run differs from a failed one.
The review works in a throwaway copy
Section titled “The review works in a throwaway copy”The review does not run in the working tree the implementation used. The runner adds a second working tree of the same commit beside the first, points the review at it, and deletes it when the review ends. A reviewer therefore reads the work rather than joining it: nothing it writes in that copy survives the review, and nothing it writes there can become part of your branch.
The branch itself is checked either side of the review:
- The runner records where the branch points before the review starts, and reads it again afterwards. If the branch moved, the runner puts it back where it was, so the implementation’s commit is what stands.
- A move is reported, not quietly corrected. The attempt carries a
branch_tamperedcheck result, visible on the run beside the review’s other checks, and an iteration whose branch was moved cannot finish as passed. - The reviewer has no git credentials. Exactly as for the coding agent, the token that can push stays outside the sandbox, so a review can never reach your remote.
When the project has no repository
Section titled “When the project has no repository”A task can be dispatched before its project has any repository attached, and it runs. There is nothing to check out, so instead of a shared per-repository workspace the runner gives that task a scratch directory of its own, initialized as an empty local git repository with no remote and no commits. Coding agents will not start without a git checkout to work in, which is the only reason it is a repository at all.
Nothing is pushed anywhere, and the directory is removed when the run ends. The work still reaches you through the run’s own timeline and artifacts. To get a branch and a commit out of a run, connect a repository to the project first.
Where the workspaces live
Section titled “Where the workspaces live”The installer points the runner at ~/.commandchain/workspaces, created with mode 0700. The directory survives upgrades and ordinary uninstalls, and is deleted only by --uninstall --purge. A repo-less task’s scratch directory lives there too, named for its run.
That matters more than it sounds: a task branch that has not been pushed yet lives there. See upgrades and uninstall.
Reviewing what came back
Section titled “Reviewing what came back”A finished coding run leaves you three things to look at: the branch on your remote, the commit reported on the run, and the run’s own timeline of what the agent did. Nothing is merged for you.
The branch is pushed before the run is finalized, so those two agree: the commit reported on the run is the commit that was reviewed, already on your remote by the time the run reaches its verdict. If a merge is requested later, it is pinned to that commit, and it is refused rather than guessed at if the branch has moved on since.
Next steps
Section titled “Next steps”- The security model: why the agent cannot reach git’s executable configuration.
- Knowledge in runs: what an agent can look up while it works.
- Troubleshooting: pushes that fail, and stashes you did not expect.