A working directory is disposable - #420
Merged
Merged
Conversation
ADR 0027 and the change documents for where working directories live and what has to leave one before it goes. The folder that used to hold repositories now holds seventeen entries, of which eleven are. One of the rest comes from this tool's default; the others have no .git at all, and a new default will not tidy those. While looking, the larger half of the problem turned up. A working directory's .openspec-ui is gitignored, so audit.jsonl cannot leave by commit, and git worktree remove without --force refuses uncommitted tracked work but does not see ignored files. Measured in a throwaway repository: the ignored log leaves the directory clean, removal exits 0, the log is gone. The tool destroys its own evidence at the moment that evidence first becomes interesting. So location and harvest are one question, and answering only the first would be choosing where to lose things. Working directories go under one root, at root/repository/change, read from the environment and a user-level file and never from the repository's own configuration: that file travels to every checkout, and a person's disk layout is not a property of the project. Not the system temporary directory, though it was proposed and the reasoning was sound as far as "these are disposable". That sentence has an "once" in it: the temporary directory is swept by things that cannot know whether the transfer happened, and a sweep bypasses the refusal that protects uncommitted work. Disposable is right; disposed of by the tool, after the transfer, is the part that has to stay true. Removal merges the directory's run history into the repository's first. Entries already carry their cwd and changeDir, so a combined log is unambiguous and de-duplicates by runId. Writes are not changed: every run keeps writing to its own directory, which is what keeps two runs off one file. Asked what else should travel, timestamps and failures are both already inside audit.jsonl. What is genuinely separate is named at removal rather than deleted quietly: the run journal, whose value expires when its runs end, and the checkpoints, whose purpose ends when the change archives. Commits are not at risk. A branch lives in the shared object store, so exactly two things can be lost: uncommitted work, which removal already refuses, and ignored files, which this change is about. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
VeryComplexAndLongName
force-pushed
the
a-working-directory-is-disposable
branch
from
September 12, 2026 09:50
955772c to
b82f1b4
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Proposal and ADR only — no implementation. Independent of the two open proposals: they deliver
shared-ui, this deliversci-cli.Why
C:\Progused to hold repositories. It now holds seventeen entries, of which eleven are. One of the rest comes from this tool's default (<repo>.worktrees/<change>); the others have no.gitat all and were made by something else. A new default will not tidy those, and this change does not pretend otherwise.While looking, the larger half of the problem turned up.
A working directory's
.openspec-ui/is gitignored, soaudit.jsonl— the run history every recommendation, timeline, cost figure and quality rating is derived from — cannot leave by commit. Andgit worktree removewithout--forcerefuses uncommitted tracked work but does not see ignored files. Measured in a throwaway repository:The tool destroys its own evidence at the moment that evidence first becomes interesting — when the work is finished. So "where do these live" and "what has to leave one before it goes" are one question, and answering only the first would be choosing where to lose things.
The decisions (ADR 0027)
One root, at
<root>/<repository>/<change>— one directory in a person's workspace folder for every repository instead of one beside each. Outside the repository, for the reasonchange-worktrees.tsalready records: a second copy inside is what every recursive tool walks into.Configured per machine, never in
openspec/config.yaml. That file travels to every checkout, and one person's disk layout is not a fact about the project. A setting in the wrong file is a setting that arrives where nobody wanted it.Not the system temporary directory. It was proposed, and the reasoning is sound as far as "these are disposable, and once their artifacts have been transferred the directory is worth nothing". It is rejected because of the once: the temporary directory is swept by Storage Sense, disk cleanup, anything deleting files past an age — and none of them can know whether the transfer happened. Deletion would be performed by the one mechanism unable to check the precondition for deleting safely. It would also bypass a refusal built on purpose, since a sweep refuses nothing where
worktree removerefuses uncommitted work. Disposable is right; disposed of by the tool, after the transfer, is the part that has to stay true.Removal harvests, then deletes. The directory's
audit.jsonlmerges into the repository's own first. Entries already carry theircwdandchangeDir, so a combined log is unambiguous and de-duplicates byrunId— harvesting twice adds nothing. Writes are unchanged: every run keeps writing to its own directory, which is what keeps two runs off one file.What is not taken is named at the point of removal — the run journal, whose value expires when its runs end, and the checkpoints, whose purpose ends when the change archives. A destroyed thing that was announced is a decision; one that was not is a discovery, made later, by whoever needed it.
The log is the artifact. Asked what else should travel — timestamps, failures — both are already inside
audit.jsonl: run identity, agent, agent version, timings, usage, outcome, summary.runTimestampsByChange,buildWorkspaceRunStats,buildVerifyQualityand the recommendations derive from it and nothing else.Commits are not at risk, and saying so bounds the problem: a branch lives in the shared object store, so exactly two things can be lost — uncommitted work, which removal already refuses, and ignored files, which this change is about.
Not in this change
Tidying directories this tool did not create: what is in them is not known here, and acting on them would be acting on a guess. Changing how a run writes its log. Backing up a working directory.
Coordination
This delivers a
ci-clidelta. Two proposals on another branch —a-doctor-says-what-would-stop-a-runanda-hint-says-what-can-run-together— deliver one too, and would meet this inopenspec/specs/ci-cli/spec.mdat archive. Proposals do not collide; implementations do. Noted at the top oftasks.md.docs/adr/README.mdwill want a one-line conflict resolve against #419, whichever merges second.🤖 Generated with Claude Code