fix(codex): hide passive hook prompts while preserving processing - #36
Merged
Merged
Conversation
ST-administrator
approved these changes
Sep 11, 2026
darrenzhu
added a commit
that referenced
this pull request
Sep 11, 2026
Review follow-up on #36. Nothing here changes the design - Stop still only queues, delivery still rides the next real prompt - but the delivery model had gaps and one state-write race. session-state.cjs - Retry the rename over the state file on EPERM/EACCES/EBUSY with a short bounded backoff. On Windows the rename fails whenever another hook has the file open, and the two PostToolUse hooks run concurrently on every tool call; a dropped write silently lost workflow-completion resets. - Owner-private state dir (0700) and temp files (0600) where honoured; clean up leftover .tmp and .display files. must-display.cjs - Keep the dedup fingerprint in a .display sidecar instead of the state file, so there is one writer per file. prompt-router.cjs / passive-observation.cjs - Emit key routing and passive context together in one additionalContext payload, so a prompt mentioning a work item (or SHA-256, CVE-...) can no longer hold back a queued checkpoint whose time is already reserved. - Linked sessions get no key advisory (canonical "silent when linked") but still receive passive context. - `defer` now re-arms the fire-once latch; stop-observer re-queues the offer after an eight-turn cooldown instead of never. - Per-prompt wake check for snoozed sessions (canonical behaviour), with the wake condition sanitised to one bounded line. - Validate queued ids as UUIDs and ship the acknowledge command ready-made in the delivered context, so the model never assembles a shell command from state-file values. - Hold delivery for exactly one prompt after a Forge clarification, keyed on a turn stamp rather than observer_blocked (which prompt-router clears before delivery runs). stop-observer.cjs - Restore the canonical skill-continuation text (SHI-787). - Batch the per-Stop writes (fewer renames, smaller race window). - Restore the load-bearing rationale comments. workflow-tracker.cjs / workflow-guard.cjs - Parse string-typed state_updates so a checkpoint never falls through to the completion reset. - Move last_checkpoint_turn with last_checkpoint_at so a freshly logged session does not queue a near-zero checkpoint on its next Stop. - Deny a replay of an already-processed checkpoint (the server adds duration deltas without dedup). - Refresh a queued checkpoint's token snapshot at call time when the Codex build can rewrite tool input. - Windows version probe: one literal command, avoiding Node 24's DEP0190 stderr warning on every tool call. Docs / tests / CI - SYNC.md: what "preserve" means, the version bump rule (including the canonical catch-up case), the missing adaptations, expected canonical test failures, and the telemetry-loss sentence corrected. - forge-autopilot SKILL.md: receipt-command contract, defer semantics, wake check, and the stale "directive" reference. - 9 new hook tests (31 total); GitHub Actions on Linux + Windows, Node 18/20/22. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… messages Codex shows a blocking Stop hook reason as an extra user message. Forge's passive session prompts (the tracking offer and periodic time checkpoints) no longer block at Stop: they are saved locally and delivered once, as developer context, with the next real prompt, so the user's answer stays the last thing on screen. A workflow step that stalls after a local skill still gets its one-time continuation reminder. - The tracking offer can be deferred; it comes back after a short cooldown. - Snoozed sessions are checked against their wake condition on each prompt. - Work-item routing hints and passive context are delivered together. - A delivered checkpoint is recorded once and cannot be resubmitted. - Local session-state writes are atomic, owner-private, and more robust on Windows. Codex plugin 1.20.1. Claude Code and Cursor are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
darrenzhu
force-pushed
the
codex/passive-hook-context
branch
from
September 11, 2026 06:56
b13ef1a to
d71e63d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Codex renders a blocking Stop hook reason as an extra user message. Forge's passive observation offers and time checkpoints now queue silently at Stop and arrive as developer context on the next real prompt, preserving the user's substantive answer. Required workflow skill continuation retains its one-time blocking reminder.
Codex plugin 1.20.3. Claude Code, Cursor, and shared server behavior are unchanged.
The passive path supports deferred offers with a cooldown, snoozed wake checks, and combined work-item routing/context delivery. State writes are atomic with bounded Windows retries, and display deduplication uses a separate sidecar.
Each queued checkpoint carries a UUID in
state_updates.codex_checkpoint_id. The guard validates the current UUID and frozen payload, then exclusively creates an attempt file before forwarding. Older IDs and duplicate/concurrent/ambiguous attempts are rejected, even after another checkpoint is delivered. The tracker acknowledges only the matching attempted ID. Both hooks normalize object or JSON-stringstate_updatesbefore inspection/enrichment; malformed input is never spread into character fields.Validation:
Limits: delivery needs a subsequent real prompt. Failed or interrupted checkpoint attempts may lose telemetry because they are not retried. Undelivered legacy queues receive their UUID on delivery; previously delivered id-less payloads are rejected. Host indicators and expandable tool activity can remain visible. Live remote checkpoint recording was not exercised.
Companion regression tests and adaptation notes: https://github.com/ShipToday/forge-mcp/pull/1014.
All three guard denial sites now use the supported PreToolUse permissionDecision/permissionDecisionReason envelope. Regression assertions validate that host contract. Codex CLI 0.149.1 recognized the updated denial as a PreToolUse block; the separate allow controls were rejected by execution policy, so a full positive-control integration pass is not claimed.