Skip to content
This repository was archived by the owner on Jun 8, 2026. It is now read-only.

FEA-1500: Obsolete; replace dead in-code anchors - #269

Closed
thadeusb wants to merge 3 commits into
mainfrom
chore/fea-1500-obsolete
Closed

thadeusb wants to merge 3 commits into
mainfrom
chore/fea-1500-obsolete

Conversation

@thadeusb

@thadeusb thadeusb commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

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 :4820 HTTP 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:

  • The :4820 HTTP call lives inside the app-owned handler scripts (resources/hooks/{hook-handler,codex-hook-handler}.js), which refreshHandlerCopy() overwrites in place on every boot.
  • ~/.claude/settings.json / ~/.codex/hooks.json only pin the handler file path + the ELECTRON_RUN_AS_NODE command — not the transport.
  • So the handler and the in-process listener ship in the same build and update together atomically when the user launches. There is no version-skew window where a handler POSTs to a transport that no longer exists.

The only genuine flaw of TCP 4820 is 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 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).
  • 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 bump 0.15.1110.15.112 (CI-enforced for any apps/desktop/ change).

Testing

Comment/doc/version-only change; no code behavior modified. The edits touch only comments, a markdown doc, and the package.json version string — none affect compilation or runtime.

Risks

None identified. No gateway route, cloud-relay message, or persisted-store schema changed; the :4820 transport behavior is unchanged.

- 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
thadeusb requested a review from a team June 5, 2026 15:42

@thadeusb thadeusb left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 shafty023 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-inline findings:

  • [P3] apps/desktop/CLAUDE.md:173 says hook events are "harness-stamped from __provider", but the listener rejects payload-level __provider and stamps provider by route (/api/hooks/event vs /api/hooks/codex/event). Please update this doc to make provider attribution route-owned and payload __provider invalid.

  • [P3] apps/desktop/CLAUDE.md:228 says any apps/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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread apps/desktop/CLAUDE.md Outdated
@@ -181,7 +181,8 @@ Gateway section remains.
hook handler POSTs to `127.0.0.1:${CLAUDE_DASHBOARD_PORT||4820}`, baked into

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

~/.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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants