fix(test): stop a fixture from re-initialising the real repository - #252
Conversation
`git` reads GIT_DIR, GIT_WORK_TREE and GIT_INDEX_FILE from the environment, and a git hook sets all three. The pre-commit gate runs the whole suite from inside `git commit`, so `workspace-diff.test.ts` calling `git init` on a temp directory did not initialise the temp directory — it re-initialised the developer's own checkout, as bare, and wrote `user.name = DeepCode Test` into its config. Every git command in that checkout then failed with "this operation must be run in a work tree". Repairing it means resetting core.bare and unsetting the injected identity; refs and objects are untouched. `collectWorkspaceDiff` itself already scrubs the environment. The fixture that tests it did not. Three other fixtures had each grown their own private copy of the scrub, and two of them carry a comment describing this exact failure. That is the tell: the protection existed, it was known, and it was being passed along by word of mouth rather than enforced — so a fixture written later simply did not get it. All four now share `gitSpawnEnv`, and a check fails the build if a test spawns `git` without it. Verified by reverting the fixture and watching the check name it. Not covered: `scripts/gen-release-notes.ts` also spawns git unscrubbed, but it runs only in the release workflow on a fresh checkout where GIT_DIR is never set, and it cannot import from the workspace without a build step it currently does not have. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`gen-release-notes.ts` walked a commit range, and with no preceding tag it fell back to the root commit. That is how v0.3.0's release page came to read "0 commits." — #250 fixed the shallow clone that produced the empty range, but the underlying choice was still to describe a release by its commit subjects. CHANGELOG.md already says what shipped, written for people, grouped by what the changes mean rather than by the verb the commit happened to start with. A list of commit subjects is what you write when nobody wrote anything better. So `--version` makes that entry the release body. The commit walk remains the fallback and announces itself, in the body and on stderr: notes generated because nobody wrote a changelog entry should not look like notes somebody wrote. Repo-relative links are rewritten to absolute URLs pinned at the tag. A release body is not rendered inside the repository, so `docs/file-contract.md` resolves against nothing and 404s; pinning at the tag rather than the default branch also keeps a v0.3.0 link pointing at the v0.3.0 document after the file moves. `[Unreleased]` cannot satisfy the lookup — a release that shipped whatever happened to be sitting under that heading would be lying about its contents. Also passes a scrubbed environment to the git calls, for the reason in #252. It duplicates six lines rather than importing `gitSpawnEnv`, because the release job runs this with `npx tsx` after install but before any build, so core's `dist/` does not exist yet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review — approveThe diagnosis is the valuable part here: three fixtures had independently grown the same scrub and two carried a comment describing this exact failure. That is the tell that the convention was being transmitted by reading neighbouring code, and the fix is right to make it mechanical rather than write a fourth comment. Verified Two limits worth knowing about, neither blockingThe check is file-level, not call-site-level.
|
`gen-release-notes.ts` walked a commit range, and with no preceding tag it fell back to the root commit. That is how v0.3.0's release page came to read "0 commits." — #250 fixed the shallow clone that produced the empty range, but the underlying choice was still to describe a release by its commit subjects. CHANGELOG.md already says what shipped, written for people, grouped by what the changes mean rather than by the verb the commit happened to start with. A list of commit subjects is what you write when nobody wrote anything better. So `--version` makes that entry the release body. The commit walk remains the fallback and announces itself, in the body and on stderr: notes generated because nobody wrote a changelog entry should not look like notes somebody wrote. Repo-relative links are rewritten to absolute URLs pinned at the tag. A release body is not rendered inside the repository, so `docs/file-contract.md` resolves against nothing and 404s; pinning at the tag rather than the default branch also keeps a v0.3.0 link pointing at the v0.3.0 document after the file moves. `[Unreleased]` cannot satisfy the lookup — a release that shipped whatever happened to be sitting under that heading would be lying about its contents. Also passes a scrubbed environment to the git calls, for the reason in #252. It duplicates six lines rather than importing `gitSpawnEnv`, because the release job runs this with `npx tsx` after install but before any build, so core's `dist/` does not exist yet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
What happened
The pre-commit gate runs the full suite from inside
git commit, which exportsGIT_DIR,GIT_WORK_TREEandGIT_INDEX_FILE.apps/server/src/workspace-diff.test.tscallsgit initon a temp directory without scrubbing them — so it did not initialise the temp directory. It re-initialised the developer's own checkout, as bare, and wrote the test identity into its config:After which every git command in that checkout fails with
fatal: this operation must be run in a work tree. Refs and objects are untouched; the repair is resettingcore.bareand unsetting the injected identity.This reproduces on
mainfor anyone who commits anything — the hook runspnpm typecheck && pnpm test, so the corruption is triggered by the act of committing, not by touching this file.Why it survived
collectWorkspaceDiff— the code under test — already callsgitSpawnEnv(). The fixture did not.Three other fixtures had each grown their own private copy of the same scrub, and two carry a comment describing this precise failure:
The protection existed, the failure mode was understood, and it was being passed along by word of mouth instead of enforced. A fixture written later simply did not get it.
Change
workspace-diff.test.tspassesgitSpawnEnv()at every git call site.gitEnvincommands.test.ts,cleanGitEnvinworktree/index.test.ts) collapse onto the canonical helper. Four call sites, one implementation.scripts/git-env-isolation.test.tsfails the build if any*.test.tsspawnsgitwithout referencinggitSpawnEnv.Verification
pnpm typecheck,pnpm lint,pnpm format:checkclean.Not covered
scripts/gen-release-notes.tsalso spawns git unscrubbed. It runs only in the release workflow on a fresh checkout whereGIT_DIRis never set, and it cannot import from the workspace without a build step that job does not have. Left for the release-notes work.🤖 Generated with Claude Code