worktree finish crashes on its own untracked .cc-os symlink #525

Closed
opened 2026-09-03 12:40:37 +00:00 by jared · 4 comments
Owner

Context

plugins/os/bin/worktree start ticket-19 printed linked: .cc-os, .sdlc/pipeline.db (it creates a .cc-os symlink in the worktree, untracked). After all commits were merged, plugins/os/bin/worktree finish ticket-19 merged fine ("Already up to date") but then raised from plugins/os/lib/worktree_cli_finish.rb:118 remove_worktree: git worktree remove failed with "contains modified or untracked files, use --force to delete it"; the only untracked entry was .cc-os, i.e. the symlink start created. Exit code was 0 despite the crash.

Expected behavior

finish removes its own linked entries (or passes --force when the only untracked paths are the ones it linked) and exits non-zero on failure.

Workaround

git worktree remove --force + git branch -d by hand.


Discoverer: hyperthrive-websites, session 01WczDUXXTYSuFuZNRQQLf49, 2026-09-03. worktree start/finish lifecycle bug preventing normal cleanup.

## Context `plugins/os/bin/worktree start ticket-19` printed `linked: .cc-os, .sdlc/pipeline.db` (it creates a `.cc-os` symlink in the worktree, untracked). After all commits were merged, `plugins/os/bin/worktree finish ticket-19` merged fine ("Already up to date") but then raised from `plugins/os/lib/worktree_cli_finish.rb:118` remove_worktree: `git worktree remove` failed with "contains modified or untracked files, use --force to delete it"; the only untracked entry was `.cc-os`, i.e. the symlink start created. Exit code was 0 despite the crash. ## Expected behavior finish removes its own linked entries (or passes --force when the only untracked paths are the ones it linked) and exits non-zero on failure. ## Workaround `git worktree remove --force` + `git branch -d` by hand. -------- **Discoverer:** hyperthrive-websites, session 01WczDUXXTYSuFuZNRQQLf49, 2026-09-03. worktree start/finish lifecycle bug preventing normal cleanup.
Author
Owner

This was generated by AI during triage.

Agent Brief

Category: bug
Summary: worktree finish must remove the entries worktree start linked, so git worktree remove succeeds without --force.

Current behavior:
worktree start symlinks .cc-os and .sdlc/pipeline.db (plus any worktree-symlinks config entries) into the new worktree. It keeps the linked list only in memory and prints it once. The root .gitignore ignores .cc-os/ as a directory. The worktree entry is a symlink, so git reports it as untracked. worktree finish merges, then calls git worktree remove without --force and without removing the links. Git refuses with "contains modified or untracked files". The RuntimeError from system(..., exception: true) propagates uncaught through Thor. A direct run exits 1 with a Ruby backtrace. The ticket's report of exit 0 came through a wrapper and did not reproduce in a direct run.

Desired behavior:
Before it removes the worktree, finish removes exactly the entries that start linked. It then runs git worktree remove without --force. If untracked paths remain that start did not create, finish refuses with a message that names those paths and exits non-zero. It never passes --force and never deletes user files. A failure in git worktree remove produces a one-line error, not a backtrace, and a non-zero exit.

Key interfaces:

  • Worktree::StartCommand (or its successor) records the linked entries somewhere finish can read back: a small manifest inside the worktree's own gitignored state, or a deterministic recomputation from the same config start used. Either is acceptable; choose one and use it for both .cc-os, .sdlc/pipeline.db, and config extras.
  • Worktree::FinishCommand#remove_worktree (or its successor) unlinks the recorded entries, then removes the worktree.
  • The Thor finish command maps a removal failure to a clean error message and a non-zero exit code.

Acceptance criteria:

  • Given a worktree created by start with the default links, when all commits are merged and finish runs, then the worktree and branch are gone and exit is 0.
  • Given a worktree whose only untracked entries are the ones start linked, when finish runs, then it does not pass --force to git.
  • Given a worktree with an extra untracked file the user created, when finish runs, then it refuses, names the file, leaves the worktree in place, and exits non-zero.
  • Given git worktree remove fails for any reason, when finish runs, then stderr has one error line with no Ruby backtrace and exit is non-zero.
  • A regression test sits beside the existing clean-path finish test and covers the .cc-os symlink case.

Out of scope:

  • Changing the .gitignore pattern for .cc-os/ (a symlink-aware pattern would hide the symptom, not fix cleanup).
  • The three start test files that lack a require_relative line. That is a separate ticket.
  • Any change to how ADR-0143 chooses what to symlink.

Governing ADRs: ADR-0143 (symlink owning state), ADR-0151 (Thor worktree CLI), ADR-0157 (CLI sole creator and destroyer).

> *This was generated by AI during triage.* ## Agent Brief **Category:** bug **Summary:** `worktree finish` must remove the entries `worktree start` linked, so `git worktree remove` succeeds without `--force`. **Current behavior:** `worktree start` symlinks `.cc-os` and `.sdlc/pipeline.db` (plus any `worktree-symlinks` config entries) into the new worktree. It keeps the linked list only in memory and prints it once. The root `.gitignore` ignores `.cc-os/` as a directory. The worktree entry is a symlink, so git reports it as untracked. `worktree finish` merges, then calls `git worktree remove` without `--force` and without removing the links. Git refuses with "contains modified or untracked files". The `RuntimeError` from `system(..., exception: true)` propagates uncaught through Thor. A direct run exits 1 with a Ruby backtrace. The ticket's report of exit 0 came through a wrapper and did not reproduce in a direct run. **Desired behavior:** Before it removes the worktree, `finish` removes exactly the entries that `start` linked. It then runs `git worktree remove` without `--force`. If untracked paths remain that `start` did not create, `finish` refuses with a message that names those paths and exits non-zero. It never passes `--force` and never deletes user files. A failure in `git worktree remove` produces a one-line error, not a backtrace, and a non-zero exit. **Key interfaces:** - `Worktree::StartCommand` (or its successor) records the linked entries somewhere `finish` can read back: a small manifest inside the worktree's own gitignored state, or a deterministic recomputation from the same config `start` used. Either is acceptable; choose one and use it for both `.cc-os`, `.sdlc/pipeline.db`, and config extras. - `Worktree::FinishCommand#remove_worktree` (or its successor) unlinks the recorded entries, then removes the worktree. - The Thor `finish` command maps a removal failure to a clean error message and a non-zero exit code. **Acceptance criteria:** - [ ] Given a worktree created by `start` with the default links, when all commits are merged and `finish` runs, then the worktree and branch are gone and exit is 0. - [ ] Given a worktree whose only untracked entries are the ones `start` linked, when `finish` runs, then it does not pass `--force` to git. - [ ] Given a worktree with an extra untracked file the user created, when `finish` runs, then it refuses, names the file, leaves the worktree in place, and exits non-zero. - [ ] Given `git worktree remove` fails for any reason, when `finish` runs, then stderr has one error line with no Ruby backtrace and exit is non-zero. - [ ] A regression test sits beside the existing clean-path finish test and covers the `.cc-os` symlink case. **Out of scope:** - Changing the `.gitignore` pattern for `.cc-os/` (a symlink-aware pattern would hide the symptom, not fix cleanup). - The three start test files that lack a `require_relative` line. That is a separate ticket. - Any change to how ADR-0143 chooses what to symlink. Governing ADRs: ADR-0143 (symlink owning state), ADR-0151 (Thor worktree CLI), ADR-0157 (CLI sole creator and destroyer).
Author
Owner

This was generated by AI during triage.

Work started via /os-sdlc:implement on branch ticket-525 (worktree at .claude/worktrees/ticket-525), 2026-09-08.

> *This was generated by AI during triage.* Work started via /os-sdlc:implement on branch `ticket-525` (worktree at .claude/worktrees/ticket-525), 2026-09-08.
Author
Owner

Resolution

Done: worktree finish now removes the entries start linked and exits 4 with a one-line error when other untracked files remain. LinkedEntries names the linked set for both commands. Five new finish scenario tests plus an entry file.

Evidence: Branch ticket-525 commit e377c22, driven by /os-sdlc:implement (implementation 2, four behavioral increments, all gates green). Full os suite: 18 runs, 0 failures. Merge to main follows via worktree finish.

Follow-ups: #534 runner brief ignores implementation target (hot-patched for this run, restored). #535 diff-test gate loops on scenario-split tests (entry file added as workaround). #533 start test files lack require_relative. The ticket's exit-0 claim did not reproduce in a direct run; dropped.

## Resolution **Done:** worktree finish now removes the entries start linked and exits 4 with a one-line error when other untracked files remain. LinkedEntries names the linked set for both commands. Five new finish scenario tests plus an entry file. **Evidence:** Branch ticket-525 commit e377c22, driven by /os-sdlc:implement (implementation 2, four behavioral increments, all gates green). Full os suite: 18 runs, 0 failures. Merge to main follows via worktree finish. **Follow-ups:** #534 runner brief ignores implementation target (hot-patched for this run, restored). #535 diff-test gate loops on scenario-split tests (entry file added as workaround). #533 start test files lack require_relative. The ticket's exit-0 claim did not reproduce in a direct run; dropped.
jared closed this issue 2026-09-08 18:05:32 +00:00
Author
Owner

This was generated by AI during triage.

Live-verified after merge: the pre-merge finish (old code) crashed on the .cc-os symlink; the re-run with the merged code removed the worktree and branch cleanly. The reported exit 0 is explained: the crash exits 1, but a pipe such as worktree finish x 2>&1 | tail reports the last command's status. Run it unpiped or read pipestatus.

> *This was generated by AI during triage.* Live-verified after merge: the pre-merge finish (old code) crashed on the .cc-os symlink; the re-run with the merged code removed the worktree and branch cleanly. The reported exit 0 is explained: the crash exits 1, but a pipe such as `worktree finish x 2>&1 | tail` reports the last command's status. Run it unpiped or read pipestatus.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jared/cc-os#525
No description provided.