From dcd9af4806376caf091e18f669ab52f1ba0dc9df Mon Sep 17 00:00:00 2001 From: kami Date: Fri, 28 Aug 2026 03:45:40 +0400 Subject: [PATCH] Exclude the control directory, not the ignored marker inside it F42, live on run 5's completion tail. The review agent wrote .orchestra/done, the worker recognised it, confirmed the agent idle, and then failed the result commit on every attempt: stage result: The following paths are ignored by one of your .gitignore files: .orchestra/done .orchestra carries a .gitignore of "*" (internal/herdr/adapter.go:132), so the marker is ignored, and git refuses an add whose pathspec names an ignored path. The exclusion now names the directory. Reproduced against git 2.55.0 in a scratch repo both ways, and the regression test uses the same constant the worker passes to git. The failure retried every five seconds for 22 minutes with the task stuck in review and nothing observable outside the journal, because each identical error overwrote the single last_error slot. That is F18, still open. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01CVbaKucEYBjMqVeUgJUsc1 --- cmd/orchestra-worker/main.go | 12 ++++++- cmd/orchestra-worker/main_test.go | 57 +++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/cmd/orchestra-worker/main.go b/cmd/orchestra-worker/main.go index edd468a..a350959 100644 --- a/cmd/orchestra-worker/main.go +++ b/cmd/orchestra-worker/main.go @@ -962,7 +962,7 @@ func (w *worker) finalize(ctx context.Context, id string, s herdr.Session) (comp if _, err := git(ctx, s.Worktree, "diff", "--quiet", "--", "TASK.md"); err != nil { return completionEvidence{}, errors.New("TASK.md was modified") } - if out, err := git(ctx, s.Worktree, "add", "-A", "--", ".", ":!.orchestra/done"); err != nil { + if out, err := git(ctx, s.Worktree, "add", "-A", "--", ".", stageExclude); err != nil { return completionEvidence{}, fmt.Errorf("stage result: %s: %w", out, err) } if _, err := git(ctx, s.Worktree, "diff", "--cached", "--quiet"); err != nil { @@ -990,6 +990,16 @@ func (w *worker) finalize(ctx context.Context, id string, s herdr.Session) (comp return e, nil } +// stageExclude keeps Orchestra's own control files out of the result commit. +// It names the directory, not the marker inside it: .orchestra carries a +// .gitignore of "*" (herdr/adapter.go), and naming an ignored file in a +// pathspec makes git refuse the whole add with "The following paths are +// ignored by one of your .gitignore files". F42, live on run 5: the review +// agent wrote .orchestra/done, and every completion attempt failed on that +// refusal, once every five seconds, with the task stuck in review. Excluding +// the directory also holds for a worktree that has no inner .gitignore. +const stageExclude = ":!.orchestra" + func (w *worker) renewLeases(ctx context.Context) { if w.executionBackend() == nil { return diff --git a/cmd/orchestra-worker/main_test.go b/cmd/orchestra-worker/main_test.go index a4967c8..f4fcabe 100644 --- a/cmd/orchestra-worker/main_test.go +++ b/cmd/orchestra-worker/main_test.go @@ -1045,3 +1045,60 @@ func deadTmuxBackend(t *testing.T) *herdr.TmuxBackend { } return &herdr.TmuxBackend{Socket: "gone", Binary: stub} } + +// F42, live on run 5: the review agent wrote .orchestra/done, and the result +// commit's staging step named that file in an exclude pathspec. Git refuses the +// whole add when a pathspec names an ignored path, so completion failed every +// five seconds and the task never left review. The marker directory ignores +// itself, so the exclusion has to name the directory. +func TestStageExcludeSurvivesTheIgnoredMarker(t *testing.T) { + repo := t.TempDir() + run := func(args ...string) (string, error) { + cmd := exec.Command("git", args...) + cmd.Dir = repo + out, err := cmd.CombinedOutput() + return string(out), err + } + for _, args := range [][]string{ + {"init", "-q", "."}, + {"config", "user.email", "t@example.invalid"}, + {"config", "user.name", "t"}, + } { + if out, err := run(args...); err != nil { + t.Fatalf("git %v: %s: %v", args, out, err) + } + } + if err := os.WriteFile(filepath.Join(repo, "a.txt"), []byte("one\n"), 0o644); err != nil { + t.Fatal(err) + } + if out, err := run("add", "a.txt"); err != nil { + t.Fatalf("%s: %v", out, err) + } + if out, err := run("commit", "-qm", "init"); err != nil { + t.Fatalf("%s: %v", out, err) + } + if err := os.WriteFile(filepath.Join(repo, "a.txt"), []byte("one\ntwo\n"), 0o644); err != nil { + t.Fatal(err) + } + control := filepath.Join(repo, ".orchestra") + if err := os.MkdirAll(control, 0o755); err != nil { + t.Fatal(err) + } + // Exactly what the adapter writes, and the marker the agent writes. + if err := os.WriteFile(filepath.Join(control, ".gitignore"), []byte("*\n"), 0o644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(control, "done"), nil, 0o644); err != nil { + t.Fatal(err) + } + if out, err := run("add", "-A", "--", ".", stageExclude); err != nil { + t.Fatalf("staging refused the ignored marker: %s: %v", out, err) + } + staged, err := run("diff", "--cached", "--name-only") + if err != nil { + t.Fatalf("%s: %v", staged, err) + } + if strings.TrimSpace(staged) != "a.txt" { + t.Fatalf("staged %q, want only a.txt", strings.TrimSpace(staged)) + } +}