hermes - 💡(How to fix) Fix Bug: /goal `✓ Goal achieved` notice silently dropped — generation mismatch in post-delivery callback store

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

When /goal judge returns verdict=done, the ✓ Goal achieved status notice never reaches the user (Discord, in my case). The goal loop itself works correctly — judge fires, state transitions to done — but the user-visible confirmation message is silently dropped.

Root Cause

Suspected root cause

Reading gateway/platforms/base.py:2436-2457 (pop_post_delivery_callback) and gateway/run.py:10591-10606 (_defer_goal_status_notice_after_delivery):

Fix Action

Workaround

Just keep running without /goal — the agent works fine; only the post-delivery confirmation notice is missing. No data loss, no stuck state.

Code Example

15:08:38,393  hermes_cli.goals: goal judge: verdict=done reason=The response explicitly confirms…

---

15:08:38,411  [Discord] Sending response (990 chars) to <chat>

---

if generation is not None:
      return None
RAW_BUFFERClick to expand / collapse

Summary

When /goal judge returns verdict=done, the ✓ Goal achieved status notice never reaches the user (Discord, in my case). The goal loop itself works correctly — judge fires, state transitions to done — but the user-visible confirmation message is silently dropped.

Environment

  • Hermes: 1e71b7180 (current main)
  • Platform: Discord gateway
  • Provider/model: copilot / claude-opus-4.7 (judge: copilot / gpt-4o-mini)
  • Host: Raspberry Pi 4, Linux aarch64

Reproduction

  1. Set auxiliary.goal_judge to a working model.
  2. In Discord, send /goal <something achievable in 1–2 turns>.
  3. Let the agent answer; judge runs and returns verdict=done.
  4. The final assistant reply is delivered to Discord, but the follow-up ✓ Goal achieved … line never appears.

Observed in logs

For my run, ~/.hermes/logs/agent.log shows the judge firing:

15:08:38,393  hermes_cli.goals: goal judge: verdict=done reason=The response explicitly confirms…

And ~/.hermes/logs/gateway.log shows the main response delivered:

15:08:38,411  [Discord] Sending response (990 chars) to <chat>

But there is zero trace of _send_goal_status_notice / register_post_delivery_callback / _defer_goal_status_notice_after_delivery ever running. The callback path is silent end-to-end.

Suspected root cause

Reading gateway/platforms/base.py:2436-2457 (pop_post_delivery_callback) and gateway/run.py:10591-10606 (_defer_goal_status_notice_after_delivery):

  • Register-side (run.py:10593-10601) reads generation from adapter._active_sessions[session_key]._hermes_run_generation. If that attribute is None at register time (e.g. the active-session Event was replaced between bind and register, or bind never ran for this run), the callback is stored as a raw callable (base.py:2432), NOT as a (gen, callback) tuple.
  • Pop-side (base.py:2454-2455) is called with generation=<int> (snapshotted from interrupt_event._hermes_run_generation in base.py:3467-3471). When entry is a raw callable but generation is not None, the code returns None and silently drops the callback:
    if generation is not None:
        return None
    The pop also deletes nothing, but since the gateway then drops the reference, the deferred ✓ Goal achieved send never fires.

So the bug is a generation-vs-no-generation mismatch in the post-delivery callback store: register writes a raw entry, pop demands a tuple, and the silent-drop path eats the callback.

Proposed fix (sketch — not opening a PR yet, want maintainer guidance)

Two minimum-risk options:

  1. Symmetric storage: always store (gen_or_None, callback) tuples in _post_delivery_callbacks, and update pop_post_delivery_callback to treat entry_generation is None as "match anything". That way the silent-drop path becomes unreachable.
  2. Hardened register: in _defer_goal_status_notice_after_delivery, if generation is None, refuse to register and fall through to await _deliver() immediately. Less elegant but eliminates the "register raw / pop tuple" mismatch.

Happy to send a PR for option 1 if that direction is acceptable.

Diagnostic patch (already applied locally)

I've added 3 INFO-level goal-debug: log lines at register / pop / fallback to confirm the path on the next repro:

  • gateway/run.py:_defer_goal_status_notice_after_delivery — log (active_obj_id, generation) at register and a fallback line when registration isn't taken.
  • gateway/platforms/base.py:pop_post_delivery_callback — log entry_gen, asked_gen, and match result.

I'll attach the next log capture once I repro with these in place.

Workaround

Just keep running without /goal — the agent works fine; only the post-delivery confirmation notice is missing. No data loss, no stuck state.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING