/goal hook fires "Goal Achieved" while TaskList tasks still pending/in_progress

Status Fixed / completed
Maintainer reply None cached
Activity 3 comments · opened May 12, 2026 · closed May 25, 2026

Summary

The session-scoped /goal Stop hook fires a "Goal Achieved" notification mid-execution, even when the agent still has tracked tasks in pending / in_progress status via TaskCreate / TaskList. This produces user-visible confusion: the UI says the goal is achieved, but the agent reports the work isn't done.

Repro

  1. Start a long multi-step implementation in a single session.
  2. /goal write plan, implement plan
  3. Agent writes an 11-task plan via writing-plans skill and starts executing through subagent-driven-development.
  4. Tasks 1–8 land as commits. Tasks 9 (parity sweep), 10 (backtest validation), 11 (final verification + handoff) still tracked as pending / in_progress.
  5. UI surfaces "Goal Achieved" while the agent is still mid-execution.
  6. User asks "is this done?" — agent (correctly) answers "no, 3 tasks remain."

Expected

Either:

  • The /goal hook consults TaskList and only fires when no pending / in_progress items remain, OR
  • The notification text is clarified to indicate it's an advisory match against the natural-language condition, not a definitive completion check.

Actual

The hook appears to evaluate the natural-language goal condition against some loose heuristic (presence of plan + most impl commits) without consulting the structured task tracker. Result: false-positive "achieved" while tracked work remains.

Environment

  • Claude Code CLI
  • Model: claude-opus-4-7
  • Long-running background session with 30+ subagent dispatches
  • Goal text: write plan, implement plan

Why it matters

Users rely on hook notifications as a signal of completion. A premature "achieved" undermines that signal and forces users to manually cross-check TaskList to know what's actually done.

View original on GitHub ↗

3 Comments

yurukusa · 5 months ago

The shape of this bug looks structurally identical to a class of divergence I've been tracking across the issue tracker: a confirmation surface fires against a natural-language heuristic while a structured information source in the same session contradicts it. Issue #57271 has the same shape with the rendering layer: the agent claims a report was "verified" against a reference, but the rendering layer was never actually consulted, only the parsed numeric tokens. The Goal Achieved notification in this issue lives at the same layer of mismatch.

The interesting design question your repro raises is which information source the hook should treat as authoritative when they disagree. The natural-language goal text says "write plan, implement plan", and that is what the heuristic is matching against. Eight commits have landed; the heuristic finds enough signal to fire. The TaskList tracker says three tasks are still in flight. Both reads are honest, given their evidence; the hook only sees one.

A correctness-oriented fix is Expected #1 from your post: the hook consults TaskList when it exists in the session, and treats any pending / in_progress items as a hard gate against firing. The edge case is sessions without a TaskList. The hook should not require its presence (many workflows don't use the structured tracker), but the rule is monotonic: if TaskList exists and has open items, the goal is by definition not yet achieved. There's no case where TaskList says "open" and the goal is correctly fireable.

A communication-oriented fix is Expected #2: rename the notification to reflect what the heuristic actually does. "Goal condition matched" is the literal evaluator output. "Goal Achieved" is the operator-facing claim. Right now the two are conflated. Even if the heuristic is kept as-is, decoupling the notification text from the evaluator's confidence would surface the gap to operators who don't read the source code.

The robust answer is probably both: the hook gates on TaskList when present, and the notification text is downgraded to "Goal condition matched (no TaskList tracker, verify externally)" when TaskList is absent. That way the operator never sees a definitive completion claim without the structured evidence that supports it.

As an interim workaround for your specific session shape: a PostToolUse hook in ~/.claude/hooks/ that intercepts the Goal Achieved event and queries the TaskList tool one more time before surfacing the notification can give you the gate behavior without waiting for the upstream fix. The shape is roughly "if the most recent TaskList output contains any pending or in_progress items, suppress the achievement banner and emit Goal condition matched but TaskList shows N items open instead." It's a 20-30 line shell script that closes the false-positive surface for your workflow until the upstream behavior is settled.

This is the same defense pattern the rule-enforcement layer uses generally: the prompt-layer or heuristic-layer claim is unreliable for safety-critical signals, so the hook layer cross-checks against the structured data and gates the visible message accordingly. In your case the safety-critical signal is "is the agent done?", and the hook can answer it more reliably than the natural-language evaluator.

jlixfeld · 4 months ago

Closing — no longer tracking this on my end. Feel free to reopen if it's still relevant.

github-actions[bot] · 2 months ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.