Skip to content

Completion signal not detected when emitted as agent's final line; loop runs to idle timeout #590

@licketysplitt

Description

@licketysplitt

Summary

When my Claude Code agent emits the configured completionSignal as the very last line of its iteration, Sandcastle does not terminate the iteration. The run hangs until idleTimeoutSeconds elapses (10 minutes in my case) and then fails with AgentIdleTimeoutError — even though the work is complete and committed.

The commit the agent made is real and lands on the target branch; the only loss is that the run is reported as a failure and result.commits is empty (because commit collection runs in the failure path differently than the completion path).

Environment

  • @ai-hero/sandcastle@0.5.8
  • Node v24.14.1
  • Windows 11 + Docker Desktop 26.0.0
  • Agent: claudeCode("claude-opus-4-7", { effort: "high" })
  • Sandbox: docker()
  • branchStrategy: { type: "branch", branch: "agent/slice-1-submit" }

Configuration

const COMPLETION_SIGNAL = "<promise>SLICE_DONE_4e9c2b</promise>";

await run({
  name: "afk-issue-1",
  agent: claudeCode("claude-opus-4-7", { effort: "high" }),
  sandbox: docker(),
  branchStrategy: { type: "branch", branch: "agent/slice-1-submit" },
  prompt: `...When the slice is fully implemented, all checks are green, and your commits are on the branch, output exactly this string on its own line as the very last thing you say: ${COMPLETION_SIGNAL}...`,
  hooks: { sandbox: { onSandboxReady: [{ command: "pnpm install", timeoutMs: 600_000 }] } },
  maxIterations: 12,
  completionSignal: COMPLETION_SIGNAL,
  idleTimeoutSeconds: 600,
});

Observed log

The relevant tail of agent-…afk-issue-1.log:

All three checks pass. Slice 1 is committed on `agent/slice-1-submit`:
- `convex/schema.ts` — `sites` and `submissions` tables (plus `authTables` for Convex Auth)
- `convex/auth.ts` + `middleware.ts` — anonymous Convex Auth gating the dashboard
- ...

<promise>SLICE_DONE_4e9c2b</promise>
Agent idle for 1 minute
Agent idle for 2 minutes
Agent idle for 3 minutes
...
Agent idle for 9 minutes
Agent idle for 600 seconds — no output received. Consider increasing the idle timeout with --idle-timeout.

The marker <promise>SLICE_DONE_4e9c2b</promise> is on its own line, with no preceding or trailing characters on that line, exactly matching completionSignal.

run() then throws AgentIdleTimeoutError and result.commits is [], even though git log on the worktree confirms a real commit landed during the iteration.

Expected

After the agent emits the configured completion signal as a substring of its output, Sandcastle should:

  1. Stop the iteration loop early (per the README's "Early termination" semantics).
  2. Set result.completionSignal to the matched string.
  3. Collect commits via the success path so they appear in result.commits.

Hypothesis

It looks like the matcher isn't seeing output that arrives at the very end of an agent message — possibly a buffering issue on Claude Code's stdout stream after the agent's last assistant turn, or a race between the agent's "I'm done" signal and the substring scanner.

I'd be happy to provide the full agent log if helpful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions