Skip to content

fix(hitl): elicitation fallback for managed clients when the approval socket is unreachable#296

Merged
heznpc merged 1 commit into
mainfrom
fix/hitl-managed-headless-fallback-2026-06-10
Jun 10, 2026
Merged

fix(hitl): elicitation fallback for managed clients when the approval socket is unreachable#296
heznpc merged 1 commit into
mainfrom
fix/hitl-managed-headless-fallback-2026-06-10

Conversation

@heznpc

@heznpc heznpc commented Jun 10, 2026

Copy link
Copy Markdown
Owner

The flagship per-call HITL had a hole in the dominant deployment. Managed
clients (the Claude family) skip MCP elicitation to avoid double prompts, and
the socket channel denies when unreachable — so a default npx airmcp setup
(no menubar app, hitl level destructive-only) hard-denied every gated tool out
of the box. Issue #28's reporter resolved it by setting hitl level "off": the
safety feature was being disabled by its own UX. WWDC26 session 347 now tells
every agentic developer to ship per-call confirmation — ours has to actually
work where agents run.

Channel order now adapts to what is available, with per-call granularity
unchanged in every path (this changes the approval channel, never the
granularity — CLAUDE.md escalation reviewed):

non-managed: elicitation → socket → deny (unchanged)
managed: socket (if reachable) → elicitation → deny
with an actionable hint (menubar app / elicitation-capable
client / hitl.whitelist / hitl.level)

Implementation: HitlClient.isReachable() probes the socket without sending a
request (a successful probe leaves the connection open for the approval call);
the guard uses a NOT_HANDLED sentinel so a tool legitimately returning
undefined can never double-execute; telemetry gains an "unavailable" channel
value for the no-channel deny. When the menubar app IS running, managed
clients still use the socket exclusively — no double prompt.

Tests: 6 new behavior tests — managed+unreachable approves/denies via
elicitation (socket never asked), no-channel deny carries the actionable
message and never runs the tool, reachable socket keeps today's exact
behavior; isReachable false on dead path / true with a live listener whose
connection is reused. Suite: 1935 pass. RFC 0008 amended (Phase 1.5).

… socket is unreachable

The flagship per-call HITL had a hole in the dominant deployment. Managed
clients (the Claude family) skip MCP elicitation to avoid double prompts, and
the socket channel denies when unreachable — so a default `npx airmcp` setup
(no menubar app, hitl level destructive-only) hard-denied every gated tool out
of the box. Issue #28's reporter resolved it by setting hitl level "off": the
safety feature was being disabled by its own UX. WWDC26 session 347 now tells
every agentic developer to ship per-call confirmation — ours has to actually
work where agents run.

Channel order now adapts to what is available, with per-call granularity
unchanged in every path (this changes the approval channel, never the
granularity — CLAUDE.md escalation reviewed):

  non-managed: elicitation → socket → deny                    (unchanged)
  managed:     socket (if reachable) → elicitation → deny
               with an actionable hint (menubar app / elicitation-capable
               client / hitl.whitelist / hitl.level)

Implementation: HitlClient.isReachable() probes the socket without sending a
request (a successful probe leaves the connection open for the approval call);
the guard uses a NOT_HANDLED sentinel so a tool legitimately returning
undefined can never double-execute; telemetry gains an "unavailable" channel
value for the no-channel deny. When the menubar app IS running, managed
clients still use the socket exclusively — no double prompt.

Tests: 6 new behavior tests — managed+unreachable approves/denies via
elicitation (socket never asked), no-channel deny carries the actionable
message and never runs the tool, reachable socket keeps today's exact
behavior; isReachable false on dead path / true with a live listener whose
connection is reused. Suite: 1935 pass. RFC 0008 amended (Phase 1.5).
@heznpc heznpc enabled auto-merge (squash) June 10, 2026 06:45
@heznpc heznpc merged commit e2ffba3 into main Jun 10, 2026
4 checks passed
@heznpc heznpc deleted the fix/hitl-managed-headless-fallback-2026-06-10 branch June 10, 2026 06:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant