Skip to content

A branches/ directory that is not a git worktree is left alone, never acted on as the main checkout (#1654) - #1656

Merged
suleimansh merged 2 commits into
mainfrom
b1654-not-a-worktree
Aug 23, 2026
Merged

A branches/ directory that is not a git worktree is left alone, never acted on as the main checkout (#1654)#1656
suleimansh merged 2 commits into
mainfrom
b1654-not-a-worktree

Conversation

@suleimansh

Copy link
Copy Markdown
Contributor

🤖 automated

Closes #1654.

The bug, plainly

Git answers inside any directory of a repository. A directory under .the-framework/branches/ that is no longer a worktree — on the rig: a checkout I removed by hand, then a failed-start marker written into the path at shutdown — therefore answers for the enclosing repo, i.e. the user's own checkout. Every consumer took that answer as the run's:

  • the cleanup sweep read the run's branch as main, ran the recoverability rule on the user's checkout (git add -A + commit — the tree happened to be clean), and tried to push the user's main to origin; it failed only because origin was ahead, and logged that every pass for 12 hours
  • the rename pass linked branches/main -> tf-agent-…
  • An agent that committed nothing leaves no branch behind (#1650) #1653's first cut judged main as "holds nothing" and ran git branch -D main, stopped only by the primary checkout having it out

What changes

One question tells a checkout from residue: is git's top level this very directory (git rev-parse --show-toplevel, both sides realpath'd). isWorktreeRoot in the store, and worktreeBranch = the branch read guarded by it. Applied at every place a branches/<run> path is acted on:

  • removeProjectWorktree (teardown, the sweep, the dashboard's Remove button) refuses before any git runs: session …'s directory is not a git worktree; left alone. The sweep reports it once, like any other kept checkout. Nothing is committed, pushed, linked or deleted through it.
  • the branch-links pass reads no branch from it, so no main -> link
  • teardown records no branch from it, so the archive cannot say the run was on main
  • markFailedStart no longer writes a marker where the checkout is gone — the daemon's log line is the record instead. That is how the residue directory came to exist (second half of the issue).

The directory itself is left where it is: the framework cannot know what a non-worktree directory holds, so it reports rather than deletes. Removal is a human rm.

Verification

  • pnpm test: 1560 node + 799 dashboard, green.
  • Four new tests against real git, each verified by breaking (each fails on exactly its own guard with main's file swapped in):
    • isWorktreeRoot true for the main checkout and a linked worktree, false for a subdirectory, a residue directory, and a directory outside any repo — and the test states the bug directly: a plain currentBranch in the residue answers with the enclosing repo's branch, worktreeBranch answers nothing
    • removal of a residue directory is refused; the user's dirty main checkout is still dirty, HEAD unchanged, nothing pushed, the directory still there
    • the rename pass with default seams on a real repo: the real rename is linked, no main link for the residue (without the guard the test shows 'main' appearing)
    • the failed-start marker creates no directory where the checkout is gone
  • Not dogfooded: the rig residue was removed by hand before this was built, and reproducing it means removing a worktree under a live run. The real-git tests are the reproduction.

No per-file SPEC edits (post-wipe, #1655 regenerates them); one FEATURES-SPEC.md line.

… acted on as the main checkout (#1654)

A checkout removed by hand, then a failed-start marker written into its path,
left a directory under `branches/` that git did not know as a worktree. Git
answers inside any directory of a repository, so every command the framework
ran there acted on the enclosing repo: the sweep read the run's branch as
`main`, tried to commit the user's checkout and push the user's `main` (it
failed only because origin was ahead), the rename pass linked `main ->` the
directory, and #1653's first cut judged `main` for deletion.

The store now asks the one question that tells the two apart — is git's top
level this very directory — and every consumer of a `branches/<run>` path
goes through it: removal refuses and reports ("not a git worktree; left
alone") before any git runs, the rename pass links nothing for it, teardown
records no branch from it. And the failed-start marker is no longer written
where the checkout is gone, which is how the directory came to exist.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@suleimansh

Copy link
Copy Markdown
Contributor Author

🤖 automated

FEATURES-SPEC.md carries the new line twice:

- A directory under `branches/` that git does not know as a worktree is never committed, pushed, linked or deleted through — it is reported and left alone, so a leftover can never stand in for your own checkout
- A directory under `branches/` that git does not know as a worktree is never committed, pushed, linked or deleted through — it is reported and left alone, so a leftover can never stand in for your own checkout

An interrupted edit had already written it once before the second edit added it again. Removing the duplicate in a follow-up commit.

An interrupted edit had written it before the second edit added it again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@suleimansh

Copy link
Copy Markdown
Contributor Author

🤖 automated

Dogfooded on the rig from this branch's build, 2026-08-23 15:53 IDT — no agent needed. The residue from this morning was recreated exactly: .the-framework/branches/tf-agent-2026-08-23T09-00-00-000Z/ holding only .the-framework/agent.json (status failed) + events.jsonl, not a worktree — git rev-parse --abbrev-ref HEAD inside it answers main. And the rig's main checkout was given an uncommitted edit to README.md, the thing the old path would have swept into a commit.

Daemon restarted; its startup sweep:

kept the worktree for session 2026-08-23T09-00-00-000Z: session 2026-08-23T09-00-00-000Z's directory is not a git worktree; left alone

After: README.md still uncommitted, HEAD still 3833ed2, origin/main untouched, no main -> link in branches/, the directory still there. This morning's build on the same setup read main, committed nothing only because the tree was clean, and tried to push the user's main every pass.

The dashboard's Remove button runs the same removeProjectWorktree, so it is covered by the same verdict. Residue removed and the edit reverted afterwards; daemon back on main.

@suleimansh
suleimansh merged commit 71c7cfa into main Aug 23, 2026
2 checks passed
@suleimansh
suleimansh deleted the b1654-not-a-worktree branch August 23, 2026 12:57
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.

A branches/ directory that is no longer a git worktree is judged — committed, pushed, branch-linked — as the main checkout

1 participant