Conversation
- Mark FEA-1500 OBSOLETE on ClosedLoop. Its premise was wrong: the :4820 hook transport is not externally pinned. It lives inside the app-owned handler scripts, which refreshHandlerCopy() overwrites in place on every boot; settings.json only pins the handler path. So handler + listener update together atomically on launch, with no version-skew window. The only real flaw of TCP 4820 is rare port collision, which already degrades safely. Not worth a migration. - agent-monitor-listener.ts: replace the CLOSEDLOOP-TICKET FEA-1500 anchor with the actual design rationale (fixed-port INTERNAL contract, zero per-hook env; unix-socket alternative considered and declined). - hook-handler.js / codex-hook-handler.js: drop the "backward-compatible contract (FEA-1500)" framing; state that the handler and listener must stay in sync since they ship together. - apps/desktop/CLAUDE.md: the Fixed-port bullet now states the loopback-HTTP transport is the accepted permanent design. - Bump desktop version 0.15.111 -> 0.15.112 (CI-enforced for any apps/desktop change). Testing: Comment/doc/version-only change; no code behavior modified. Did not run full typecheck (fresh worktree has no node_modules); the edits are limited to comments, a markdown doc, and the package.json version string, none of which affect compilation or runtime. Risks: None identified. No runtime, gateway, relay, or persisted-store contract changed; the :4820 transport behavior is unchanged.
thadeusb
left a comment
There was a problem hiding this comment.
The internal-contract argument holds up. settings.json only pins the handler path and the run-as-node command, the port/path/envelope live in the handler scripts that refreshHandlerCopy rewrites on boot, so there's no skew window for FEA-1500 to guard against. Comments line up with the code now.
shafty023
left a comment
There was a problem hiding this comment.
Non-inline findings:
-
[P3]
apps/desktop/CLAUDE.md:173says hook events are "harness-stamped from__provider", but the listener rejects payload-level__providerand stamps provider by route (/api/hooks/eventvs/api/hooks/codex/event). Please update this doc to make provider attribution route-owned and payload__providerinvalid. -
[P3]
apps/desktop/CLAUDE.md:228says anyapps/desktop/change requires a clean-machine packaged-DMG smoke test. The PR currently records no local validation for this desktop change. Please either run and record that packaged-DMG smoke, or document an accepted exception for doc/comment/version-only changes.
| // /api/hooks/codex/event); this in-process listener serves it. Port + path + | ||
| // envelope must stay in sync with those handlers, but both ship in the same app | ||
| // build and refresh together (refreshHandlerCopy rewrites the userData handler | ||
| // copy on boot), so this is an INTERNAL contract — not externally pinned. 4820 is |
There was a problem hiding this comment.
refreshHandlerCopy() is boot repair and catches failures; enabled installs can keep a stale userData handler copy invoked by persistent Claude/Codex settings. Calling the port/path/envelope an INTERNAL contract risks future changes without a listener shim/transition that silently drop hooks for stale copies. Please keep the compatibility requirement explicit: route/envelope changes need backward-compatible listener support until persisted handler copies are known refreshed.
There was a problem hiding this comment.
Good catch — fixed in 6d6b1ff. Dropped the INTERNAL contract framing and made the compatibility requirement explicit: the route + envelope must stay backward-compatible because the userData handler copy refreshes only best-effort on boot (refreshHandlerCopy failures are caught/logged) and can outlive an app upgrade, so any route/envelope change needs backward-compatible receiver support until persisted copies are known refreshed. Also called out that /api/hooks/event has two receivers (legacy sidecar + in-process listener).
| * fail a Claude turn. The port + path + payload envelope are a backward-compatible | ||
| * contract baked into ~/.claude/settings.json (see FEA-1500); do not change them. | ||
| * fail a Claude turn. The port + path + payload envelope must stay in sync with | ||
| * the in-process listener (src/main/agent-monitor-listener.ts); both ship in the |
There was a problem hiding this comment.
This comment now makes the first-party handler sound paired only with AgentHookListener, but default Agent Monitor mode still starts AgentMonitorSidecar and syncAgentMonitorHooksOnBoot() installs/repairs the same handler command. While legacy sidecar remains default, /api/hooks/event compatibility has two receivers. Please scope the statement to design-system mode or say the handler envelope must stay compatible with both sidecar and in-process receivers.
There was a problem hiding this comment.
Agreed — fixed in 6d6b1ff. The comment now states the route + envelope must stay backward-compatible with BOTH receivers of /api/hooks/event: the legacy AgentMonitorSidecar (default Agent Monitor mode) and the in-process AgentHookListener (design-system mode), and notes the handler is a persisted userData copy that refreshes only best-effort on boot.
| @@ -181,7 +181,8 @@ Gateway section remains. | |||
| hook handler POSTs to `127.0.0.1:${CLAUDE_DASHBOARD_PORT||4820}`, baked into | |||
There was a problem hiding this comment.
~/.claude/settings.json does not bake CLAUDE_DASHBOARD_PORT || 4820; it stores the Electron-as-Node command pointing at the userData handler copy, and the copied handler owns the port fallback and route. Leaving this wording preserves the obsolete FEA-1500 premise while this PR is trying to remove it. Please reword this bullet so settings pin the handler path/command, while handler content owns the fixed-port fallback and envelope.
There was a problem hiding this comment.
Fixed in 6d6b1ff. Reworded the bullet so ~/.claude/settings.json pins only the Electron-as-Node command + userData handler path, while the copied handler owns the CLAUDE_DASHBOARD_PORT || 4820 fallback, route, and envelope. Also noted /api/hooks/event is served by both the legacy sidecar (default) and the in-process listener (design-system). Removes the 'baked into settings.json' premise the PR is trying to retire.
Address shafty023 review on PR #269. The earlier rewording over- corrected from "frozen external contract" to "freely-changeable internal contract"; the real (and now permanent) requirement is in between. - agent-monitor-listener.ts: /api/hooks/event has TWO receivers across modes (legacy AgentMonitorSidecar in default mode, in-process AgentHookListener in design-system mode). Drop the "INTERNAL contract" framing and state the compatibility requirement explicitly: the route + envelope must stay backward-compatible because the userData handler copy refreshes only best-effort on boot (failures caught) and can outlive an app upgrade. - hook-handler.js: envelope must stay compatible with both receivers, not only the in-process listener. - codex-hook-handler.js: same backward-compat framing for /api/hooks/codex/event. - apps/desktop/CLAUDE.md: settings.json pins only the Electron-as-Node command + handler path; the copied handler owns the CLAUDE_DASHBOARD_PORT || 4820 fallback, route, and envelope. Removes the inaccurate "baked into settings.json" premise. Testing: Comment/doc-only change; no code behavior modified. Risks: None identified. No runtime, route, or envelope behavior changed; only the documenting comments.
# Conflicts: # apps/desktop/package.json
Summary
Closes out FEA-1500 as OBSOLETE (we are not migrating the agent-monitor hook transport) and removes the now-dead in-code anchors that referenced it.
Why FEA-1500 is obsolete
FEA-1500 tracked "remove the legacy
:4820HTTP hook listener after a transport migration." Its stated premise — that the port/transport is a backward-compatible contract baked into external config that must survive "until all installs self-heal" — is incorrect about where the contract boundary sits::4820HTTP call lives inside the app-owned handler scripts (resources/hooks/{hook-handler,codex-hook-handler}.js), whichrefreshHandlerCopy()overwrites in place on every boot.~/.claude/settings.json/~/.codex/hooks.jsononly pin the handler file path + theELECTRON_RUN_AS_NODEcommand — not the transport.The only genuine flaw of TCP
4820is port collision (it's an unreserved port; a squatting process disables capture and could receive hook payloads). That's rare and already degrades safely (EADDRINUSE→ "agent monitoring is off this session"). A unix-socket swap would be a ~1-release internal change, but it isn't worth doing. The fixed-port loopback-HTTP transport is the accepted permanent design.Changes
src/main/agent-monitor-listener.ts— replace theCLOSEDLOOP-TICKET FEA-1500anchor with the actual design rationale (fixed-port internal contract, zero per-hook env; unix-socket alternative considered and declined).resources/hooks/hook-handler.js,resources/hooks/codex-hook-handler.js— drop the "backward-compatible contract (FEA-1500)" framing; state that handler + listener must stay in sync because they ship together.apps/desktop/CLAUDE.md— the Fixed-port bullet now states the loopback-HTTP transport is the accepted permanent design (was: "FEA-1500 tracks migrating this transport later").apps/desktop/package.json— version bump0.15.111→0.15.112(CI-enforced for anyapps/desktop/change).Testing
Comment/doc/version-only change; no code behavior modified. The edits touch only comments, a markdown doc, and the
package.jsonversion string — none affect compilation or runtime.Risks
None identified. No gateway route, cloud-relay message, or persisted-store schema changed; the
:4820transport behavior is unchanged.