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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVbaKucEYBjMqVeUgJUsc1
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user