Stop refusing a handoff over words the arrow already says

98f1b2d fixed the arrow and left the other half. "tried" and "failed because"
restate the field names either side of a separator that already says what the
line means, and the parser required both.

Run 9 lost three leases to this. Every refused line carried exactly the data
the fields want:

    tried run line ["bash", "scripts/test_healthcheck.sh"] for phase-3
      -> refused, policy allows only bash -n on one file
    tried finding ids "F1".."F8" in research.json
      -> schema requires lowercase letters, digits, dash or underscore

Both prefixes are now optional. A line with no cause and effect is still
refused, because the split is the thing being validated.

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 15:38:34 +04:00
parent fb7135e1d9
commit fda78cf6e6
2 changed files with 33 additions and 17 deletions
+25 -10
View File
@@ -366,30 +366,45 @@ func TestLastObservedCommandSkipsOrchestrasOwnHandoffWrite(t *testing.T) {
}
}
// Run 9 lost a lease because the agent wrote "->" instead of "→". The content
// was exactly right, the handoff was refused, and a refused handoff fails the
// release rather than the turn.
func TestDeadEndAcceptsEitherArrowAndStripsThePrefixes(t *testing.T) {
for _, arrow := range []string{"→", "->"} {
// Run 9 lost three leases to a handoff whose content was exactly right. The
// agent wrote "->" for the arrow and dropped the "tried"/"failed because"
// words, and each refusal failed the release rather than the turn.
func TestDeadEndAcceptsEitherArrowAndOptionalPrefixes(t *testing.T) {
for _, line := range []string{
`tried finding ids "F1".."F8" → failed because the schema requires lowercase`,
`tried finding ids "F1".."F8" -> failed because the schema requires lowercase`,
`finding ids "F1".."F8" -> the schema requires lowercase`,
`tried finding ids "F1".."F8" -> the schema requires lowercase`,
} {
a, err := parseHandoffAnswer(`NEXT: inspect the failing integration test
WHY: isolate the regression before changing production code
REMAINING: fix the assertion after identifying the cause
DEAD ENDS: tried finding ids "F1".."F8" ` + arrow + ` failed because the schema requires lowercase
DEAD ENDS: ` + line + `
OPEN Q: whether the remote worker has the updated fixture
LEARNED: the fixture requires a committed scratch branch
`)
if err != nil {
t.Fatalf("arrow %q: %v", arrow, err)
t.Fatalf("%q: %v", line, err)
}
if len(a.DeadEnds) != 1 {
t.Fatalf("arrow %q: parsed %d dead ends", arrow, len(a.DeadEnds))
t.Fatalf("%q: parsed %d dead ends", line, len(a.DeadEnds))
}
d := a.DeadEnds[0]
if d.Tried != `finding ids "F1".."F8"` {
t.Errorf("arrow %q: tried = %q", arrow, d.Tried)
t.Errorf("%q: tried = %q", line, d.Tried)
}
if d.WhyFailed != "the schema requires lowercase" {
t.Errorf("arrow %q: why_failed = %q", arrow, d.WhyFailed)
t.Errorf("%q: why_failed = %q", line, d.WhyFailed)
}
}
// A line with no cause and effect at all is still refused.
if _, err := parseHandoffAnswer(`NEXT: a
WHY: b
REMAINING: c
DEAD ENDS: this line names no outcome
OPEN Q: d
LEARNED: e
`); err == nil {
t.Error("a dead end with no separator was accepted")
}
}