Stop a delivered TASK.md from poisoning every later task

Delivery merges the task branch, and that branch carries the "orchestra:
TASK.md" commit. Master therefore ends up holding the previous task's TASK.md,
and the next worktree branches from it.

writeTaskFile returned early on os.Stat, so it left that inherited file in
place. The worker then hashed the current task and every immutability check
failed with "TASK.md changed" against a hash for a task nobody was running.
Releases failed, rotation never relaunched, phase requests were never read, and
the task died on retry_limit without leaving research.

Presence is not identity. Compare content, and rewrite when it differs.

Found live in run 7, the first task to start after run 6's pull request merged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVbaKucEYBjMqVeUgJUsc1
This commit is contained in:
2026-08-28 14:15:23 +04:00
parent 7700dd60c6
commit 4b320809bd
2 changed files with 58 additions and 2 deletions
+47
View File
@@ -125,3 +125,50 @@ func TestGitWorktreesCommitsTaskFile(t *testing.T) {
t.Fatalf("TASK.md changed on recreate")
}
}
// A delivered task branch carries its own TASK.md, so master ends up holding
// the previous task's file. The next worktree must not inherit it: run 7 died
// on retry_limit because every release failed "TASK.md changed" against a hash
// for a task nobody was running.
func TestWorktreeReplacesAnInheritedTaskFile(t *testing.T) {
base := t.TempDir()
repo := filepath.Join(base, "repo")
initRepo(t, repo)
previous := domain.Task{ID: "previous-task", Project: "p", Title: "the merged one"}
if err := os.WriteFile(filepath.Join(repo, "TASK.md"), continuity.RenderTaskFile(previous), 0644); err != nil {
t.Fatal(err)
}
for _, args := range [][]string{{"add", "TASK.md"}, {"commit", "-m", "merged task branch"}} {
cmd := exec.Command("git", args...)
cmd.Dir = repo
cmd.Env = append(os.Environ(), "GIT_AUTHOR_NAME=t", "GIT_AUTHOR_EMAIL=t@t", "GIT_COMMITTER_NAME=t", "GIT_COMMITTER_EMAIL=t@t")
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("git %v: %v: %s", args, err, out)
}
}
current := domain.Task{ID: "current-task", Project: "p", Title: "the one being run"}
w := orchestrator.GitWorktrees{Root: filepath.Join(base, "wt"), Repo: repo}
path, err := w.Create(context.Background(), current)
if err != nil {
t.Fatalf("create: %v", err)
}
got, err := os.ReadFile(filepath.Join(path, "TASK.md"))
if err != nil {
t.Fatal(err)
}
if string(got) != string(continuity.RenderTaskFile(current)) {
t.Fatalf("worktree inherited a stale TASK.md:\n%s", got)
}
// Committed, not left dirty: the immutability check reads git status.
cmd := exec.Command("git", "status", "--porcelain")
cmd.Dir = path
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("git status: %v: %s", err, out)
}
if strings.Contains(string(out), "TASK.md") {
t.Fatalf("TASK.md left uncommitted: %s", out)
}
}