Skip to content

A working directory is disposable - #420

Merged
VeryComplexAndLongName merged 1 commit into
mainfrom
a-working-directory-is-disposable
Sep 12, 2026
Merged

A working directory is disposable#420
VeryComplexAndLongName merged 1 commit into
mainfrom
a-working-directory-is-disposable

Conversation

@VeryComplexAndLongName

Copy link
Copy Markdown
Owner

Proposal and ADR only — no implementation. Independent of the two open proposals: they deliver shared-ui, this delivers ci-cli.

Why

C:\Prog used 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 .git at 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, so audit.jsonl — the run history every recommendation, timeline, cost figure and quality rating is derived from — 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:

ignored .openspec-ui/audit.jsonl → git status: (clean)
git worktree remove (no --force)  → exit=0
audit.jsonl                       → GONE

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 reason change-worktrees.ts already 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 remove refuses 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.jsonl merges into the repository's own first. Entries already carry their cwd and changeDir, so a combined log is unambiguous and de-duplicates by runId — 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, buildVerifyQuality and 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-cli delta. Two proposals on another branch — a-doctor-says-what-would-stop-a-run and a-hint-says-what-can-run-together — deliver one too, and would meet this in openspec/specs/ci-cli/spec.md at archive. Proposals do not collide; implementations do. Noted at the top of tasks.md.

docs/adr/README.md will want a one-line conflict resolve against #419, whichever merges second.

🤖 Generated with Claude Code

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
VeryComplexAndLongName force-pushed the a-working-directory-is-disposable branch from 955772c to b82f1b4 Compare September 12, 2026 09:50
@VeryComplexAndLongName
VeryComplexAndLongName merged commit ea5d480 into main Sep 12, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant