Skip to content

fix(lifecycle): compare committed files through Git's clean filters - #2482

Merged
Soph merged 2 commits into
mainfrom
fix/uncommitted-filter-autocrlf
Sep 15, 2026
Merged

Soph merged 2 commits into
mainfrom
fix/uncommitted-filter-autocrlf

Conversation

@Soph

@Soph Soph commented Sep 15, 2026 •

Copy link
Copy Markdown
Collaborator

https://entire.io/gh/entireio/cli/trails/1331

Summary

  • Fix already-committed files being checkpointed again at turn-end, recreating stale shadow branches—most visibly with Copilot CLI on Windows.
  • Compare worktree files to HEAD using Git’s clean-filtered hashes, correctly handling CRLF, .gitattributes, and Git LFS. Preserve raw-byte fallbacks for symlinks and hashing failures.

Testing

  • Regression tests verify committed CRLF files are dropped and real edits are kept.
  • mise run check and GOOS=windows go vet ./cmd/... passed.
  • Nightly smoke confirmation requires the first published nightly after merge.

filterToUncommittedFiles decided "already committed" by comparing the
working tree's raw bytes against the HEAD blob. Under core.autocrlf —
the Git for Windows default, and what both testutil.InitRepo and
e2e/testutil/repo.go set — the working tree holds CRLF while the blob
holds LF, so every committed text file compared unequal and the filter
never dropped anything.

That matters because the filter is what stops an agent's mid-turn commit
from being checkpointed twice. With it broken, turn-end saw a file that
PostCommit had already condensed, missed the "no changes, skip" gate, and
minted a fresh shadow branch on the *new* HEAD seconds after PostCommit
deleted the old one. Nothing condenses that branch away — the session
ends and no further commit arrives — so it outlives the session. On the
nightly install smoke that surfaced as copilot-cli failing
TestMultiSessionSequential on windows-latest with "shadow branches should
be cleaned up within 10s after commit", every night since 09-11.

The condition was latent from the start and only became reachable when
copilot-cli's turn-end file list stopped being empty. The last green
nightly ran the same broken filter, so its list was genuinely empty
rather than correctly filtered; the only extraction change in that window
is #2341's restrictedProperties.filePaths fallback. That fallback follows
Copilot moving the field out of properties, so both halves are needed to
explain the flip — and neither is at fault: reporting a path the agent
later committed is correct at that layer, and narrowing it to what is
still uncommitted is this function's job. The three other Windows agents
produce no turn-end list at all and so never reached the comparison.

Compare through gitrepo.HashWorktreeFiles (git hash-object) instead, which
applies Git's path-specific clean filters. This also fixes .gitattributes
eol/text rules and clean filters such as Git LFS, which the byte
comparison got wrong for the same reason. Symlinks stay on the raw path:
hash-object follows the link and hashes the target's content, while a Git
symlink blob stores the target path. Anything Git cannot hash falls back
to the previous comparison, so the function still fails open — toward
keeping a file rather than dropping one.

Both call sites benefit: turn-end and the subagent task path, whose own
comment already described this exact failure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 01M2JJHKDTXB4SAFGYRC6FWRWJ
@Soph
Soph requested a review from a team as a code owner September 15, 2026 13:36
Copilot AI lite review requested due to automatic review settings September 15, 2026 13:36

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 1a16ed8. Configure here.

Comment thread cmd/entire/cli/state.go
nodo
nodo previously approved these changes Sep 15, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Address the unresolved worktree entry validation issue before approval.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Fixes lifecycle file filtering by comparing worktree files through Git’s clean filters, preventing redundant checkpoints under CRLF and similar configurations.

Changes:

  • Uses HashWorktreeFiles for filter-aware comparisons.
  • Preserves conservative fallback behavior.
  • Adds CRLF regression tests for clean and modified files.
File summaries
File Summary
cmd/entire/cli/state.go Implements Git-aware filtering. Moderate issue: validate worktree entries are regular files before hashing to avoid symlink/type-change misclassification or FIFO blocking.
cmd/entire/cli/state_test.go Adds CRLF handling regression coverage.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread cmd/entire/cli/state.go Outdated
Checking only the HEAD entry's mode was not enough. git hash-object
follows a working-tree symlink and hashes the target's *content*, so a
tracked regular file replaced by a link to identical content hashes equal
to its HEAD blob and was dropped as "already committed" — while git status
calls it a typechange (" T"). The HEAD entry is still a regular file, so
the tree mode cannot catch it. Verified directly: after replacing a
committed foo.txt with a symlink to a file of equal content,
`git hash-object -- foo.txt` returns the HEAD blob hash exactly.

That drops a real change, which is the direction this function must never
fail in. A FIFO is worse than wrong: hash-object blocks reading it, and on
a hook path that costs the caller its whole budget.

strategy already had this rule and a test asserting it, so rather than add
a third copy the pair moves to worktreedir.HashableEntry, where both
callers reach it. It cannot live beside HashWorktreeFiles in gitrepo:
worktreedir's own test imports testutil, which imports gitrepo, so that
edge is an import cycle in the test binary. Its tests move with it, and
content_overlap_test keeps the half that is still its own — what the
confined fallback answers for a symlink.

Reported by Cursor Bugbot and Copilot on #2482, both pointing at
strategy.requiresConfinedWorktreeHash as the precedent. They were right.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 01M2JNBMD9081P0M1ZTX1KKXM2
@Soph
Soph merged commit 5950974 into main Sep 15, 2026
15 of 16 checks passed
@Soph
Soph deleted the fix/uncommitted-filter-autocrlf branch September 15, 2026 14:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants