Skip to content

fix: capture files when git HEAD is unborn - #1016

Open
ethanhawkes-gif wants to merge 1 commit into
Nano-Collective:mainfrom
ethanhawkes-gif:fix/unborn-head-snapshots
Open

fix: capture files when git HEAD is unborn#1016
ethanhawkes-gif wants to merge 1 commit into
Nano-Collective:mainfrom
ethanhawkes-gif:fix/unborn-head-snapshots

Conversation

@ethanhawkes-gif

Copy link
Copy Markdown
Contributor

Closes #1010.

Summary

  • detect whether HEAD exists before asking Git for tracked modifications
  • continue collecting untracked files with git ls-files --others --exclude-standard when HEAD is unborn
  • add a regression test for a new repository containing root, nested, and ignored files

Why this works

An unborn HEAD means there are no tracked files to diff, but it does not prevent git ls-files --others --exclude-standard from listing untracked files. The existing ignore filtering and checkpoint file limit remain unchanged. In repositories with a valid HEAD, the existing git diff --name-only HEAD path still runs.

Tests

  • pnpm run test:ava source/services/file-snapshot.spec.ts — 28 passed, including FileSnapshotService finds untracked files when git HEAD is unborn
  • pnpm run test:types — passed
  • pnpm exec biome check source/services/file-snapshot.ts source/services/file-snapshot.spec.ts — passed
  • pnpm run build — passed

@github-actions

Copy link
Copy Markdown
Contributor

No changeset found

This PR does not add a changeset, so it will not appear in the changelog or trigger a release.

If the change is user-facing, add one:

pnpm changeset

Pick a bump (patch / minor / major) and write the changelog entry in our usual voice ("Added X... Thanks to @you. Closes #123."), then commit the generated .changeset/*.md file.

If this PR is docs-only or a chore that needs no release note, you can ignore this - or run pnpm changeset --empty to record that intentionally.

@will-lamerton will-lamerton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this - the diagnosis is right and I reproduced it: with an unborn HEAD git diff --name-only HEAD exits 128 and takes the whole try block down, while git ls-files --others --exclude-standard returns exactly the files your test asserts.

Two things before this can land.

1. The branch is on a stale base and will conflict. The merge base is b3eb0349, but main has since moved this code (e2770f18, 1d0cf103). getModifiedFiles() is now a one-line delegate and the body you patched lives in getModifiedFilesResult(). Please rebase and re-apply the fix there.

This matters beyond the conflict: that function now returns an available flag, and source/acp/acp-timeline.ts skips timeline checkpointing entirely when it is false. Today an unborn repo reads as "git unavailable"; after the fix it should be available: true with the untracked files listed.

2. Drop the probe, catch the diff instead. getModifiedFiles runs on every checkpoint and twice per ACP tool call, so git rev-parse --verify HEAD adds a spawn to every call on the common path where HEAD exists. Same result, one spawn:

let modifiedOutput = '';
try {
	modifiedOutput = execSync('git diff --name-only HEAD', {
		cwd: this.workspaceRoot,
		encoding: 'utf-8',
		stdio: ['pipe', 'pipe', 'pipe'],
	}).trim();
} catch {
	// Unborn HEAD: no tracked files to diff, untracked scan below still works.
}

The available: false contract survives, since a non-git directory still throws at the git ls-files call right below.

Smaller points:

  • Point the test at getModifiedFilesResult() after the rebase and assert available: true / truncated: false alongside the file list. That is the assertion that catches a regression breaking ACP checkpoints in fresh repos.
  • The exact t.deepEqual is sensitive to a contributor's global core.excludesFile or init.templateDir, which do apply to the nested repo. git -c core.excludesFile=/dev/null init plus GIT_CONFIG_GLOBAL=/dev/null in the env makes it deterministic. Also worth flagging that this is the first git-invoking test in the file.
  • files.sort() mutates the array the service returned; [...files].sort() is safer.
  • The file already imports execFileSync and uses the arg-array form for git show. No injection risk here, but the new call drifts from where the rest of the repo is going.
  • Please add a changeset so this shows up in the release notes.

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Hi @ethanhawkes-gif, thanks for this PR! It looks like a codeowner has left feedback
or review activity and there are still some outstanding items to wrap up.

Whenever you get a chance, could you take a look at the open comments?
If anything is unclear or you'd like a hand, just reply here and we'll help you get it across the line.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] git diff --name-only HEAD fails silently in new git repos

2 participants