Skip to content

Commit 11a1038

Browse files
jvalin17claude
andcommitted
Fix goalless auto-restart: handle placeholder goal and headless EOFError
_resolve_goal now recognizes the hook placeholder "(no goal set...)" and continues with a fallback instead of using it as a real goal. Also catches EOFError/KeyboardInterrupt from input() so headless mode exits cleanly instead of crashing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4701552 commit 11a1038

2 files changed

Lines changed: 44 additions & 5 deletions

File tree

scripts/auto_continue.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,12 @@ def run(self) -> int:
8383
)
8484

8585
def _resolve_goal(self) -> str:
86-
"""Goal from: arg > HANDOFF.md > interactive prompt."""
86+
"""Goal from: arg > HANDOFF.md > interactive prompt > empty string.
87+
88+
When HANDOFF.md exists (auto-restart), a missing goal is acceptable —
89+
the agent reads HANDOFF.md itself. When no HANDOFF.md and no arg,
90+
try interactive prompt but handle headless (EOFError) gracefully.
91+
"""
8792
if self.goal:
8893
return self.goal
8994

@@ -96,10 +101,18 @@ def _resolve_goal(self) -> str:
96101
re.DOTALL,
97102
)
98103
if goal_match:
99-
return goal_match.group(1).strip()
100-
101-
# Interactive prompt
102-
return input("What's the goal? ")
104+
extracted = goal_match.group(1).strip()
105+
# Ignore hook placeholder — treat as "no goal" but continue
106+
if not extracted.startswith("(no goal"):
107+
return extracted
108+
# HANDOFF.md exists but no real goal — continue anyway
109+
return "(continued from HANDOFF.md)"
110+
111+
# Interactive prompt — handle headless mode
112+
try:
113+
return input("What's the goal? ")
114+
except (EOFError, KeyboardInterrupt):
115+
return ""
103116

104117
def _build_prompt(self) -> str:
105118
"""Build the prompt for the Claude session."""

tests/test_auto_continue.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,32 @@ def mock_launch(prompt):
297297
# --- CLI parsing ---
298298

299299

300+
class TestGoallessRestart:
301+
"""Bug: auto-restart without goal arg should work when HANDOFF.md exists."""
302+
303+
def test_no_goal_arg_with_handoff_continues(self, project_dir):
304+
"""When HANDOFF.md exists but no goal arg, wrapper should continue (not block on input)."""
305+
handoff = (
306+
"# HANDOFF\n\n## Goal\n\n"
307+
"(no goal set — check project-state.md)\n\n"
308+
"## Session\n\nNumber: 3\n"
309+
)
310+
(project_dir / "HANDOFF.md").write_text(handoff)
311+
312+
runner = AutoContinue(goal=None, max_budget=None, project_dir=project_dir, dry_run=True)
313+
# Should NOT call input() — should resolve goal from handoff or use fallback
314+
result = runner.run()
315+
assert result == 0
316+
317+
def test_no_goal_no_handoff_returns_error_in_headless(self, project_dir):
318+
"""No goal + no HANDOFF.md + non-interactive = exit 1, not hang on input()."""
319+
runner = AutoContinue(goal=None, max_budget=None, project_dir=project_dir)
320+
# Simulate non-interactive (stdin closed)
321+
with patch("builtins.input", side_effect=EOFError):
322+
result = runner.run()
323+
assert result == 1
324+
325+
300326
class TestParseArgs:
301327
def test_goal_positional(self):
302328
args = parse_args(["Build auth system"])

0 commit comments

Comments
 (0)