specs/003-inline-approvals/probe-results.md

Claude approval channel: empirical results

Historical probe artifact. Prepared against commit eaf8076 on 2026-10-02. The updated repository already implements native permission replies through spec 013 and ADR-0015. This document records the probe design, not the current implementation contract.

The synchronous Claude PermissionRequest hook can accept or deny a Bash permission while Claude keeps its terminal UI. Both outcomes were verified inside signaltty. This establishes a response channel for the first adapter; it does not implement the server bridge or the GUI buttons.

Environment and procedure

The probes ran on Linux on 2026-10-02. The initial Claude version was 2.1.287. Later terminal headers showed 2.1.288. Both versions appear in the structured evidence. Codex 0.160.0 was inspected with --help; no Codex approval channel was tested.

Each live session used Haiku, a temporary working directory, an explicit settings file, empty normal setting sources, and an empty strict MCP config. No permission bypass flag was used. Authentication used the existing Claude login. Sessions can create their own transcript and workspace trust records; the probe does not install hooks into personal settings.

The requested command was printf approved > approval-proof.txt. A PreToolUse hook returned permissionDecision: "ask" to force a permission request for this harmless command. The PermissionRequest hook recorded its input, waited for an external response file, then emitted the Claude verdict. For an approval, the file had to contain approved. For a denial, the file had to remain absent and the terminal had to report hook denial.

The response file represented the future GUI selection. This was a live CLI experiment, not a click through the current signaltty GUI.

Observed outcomes

CaseObservation
Headless PreToolUse allowThe command executed and created the marker.
Headless PreToolUse denyNo marker; Claude reported a permission denial.
Interactive PermissionRequest allowThe external verdict created the marker.
Interactive PermissionRequest denyNo marker; the TUI reported hook denial.
Direct terminal answerEnter on the displayed Yes option created the marker while the hook still waited.
Hook timeout, 2 secondsClaude terminated the hook. The terminal permission prompt remained usable.
External allow after timeoutNo marker during the observation interval. Enter in the terminal then created it.
Late deny after terminal approvalThe hook emitted the late verdict, but the already approved command remained completed.
Semantic completionPostToolUse arrived with the same tool name and input before the waiting permission hook returned.
Two simultaneous signaltty panesAllow executed only in its pane; deny did not execute in its pane. Each hook received its correct SIGNALTTY_PANE and isolated SIGNALTTY_SOCKET.

The signaltty cases used a separate server, socket, state directory, and empty plugin directory. The panes were closed and the server was shut down afterward. The controller used the existing IPC for spawn, read, and terminal input; approval itself used hook stdout, not synthetic permission keys.

Consequences for implementation

Choose Claude hook verdicts as the first channel. Initially offer Once and Deny for Bash permissions. Keep Always, plan approval, user questions, and other adapters read-only until their own probes pass.

The hook input did not contain tool_use_id. Generate an opaque decision ID when the server registers the pending request. Do not derive identity from the displayed question or terminal text.

Use a dedicated held IPC connection for the hook response. Register the response receiver before publishing an actionable decision. Watch the connection and deadline concurrently, and invalidate the decision before yielding to terminal handling. This is the chosen design direction in the plan.

Transport lifetime alone cannot indicate resolution. A terminal answer can win while the hook still waits. Use semantic signals as well, and retain the distinction between a server accepting a choice and Claude applying it. The probes establish an at-most-once server delivery target, not a protocol acknowledgement from Claude.

pane.mark_seen currently clears attention on focus. Focus must not consume the future decision. Persist no actionable hook transport across restart.

Remaining acceptance checks

The implementation still needs duplicate-client and stale-ID arbitration, supersession, disconnect cleanup, restart cleanup, and a real GUI click. Direct terminal approval during a long command, direct terminal denial, and Escape need separate cancellation tests. PostToolUse proves completion, but it does not prove that the hook stops when execution starts.

The timeout case proves rejection of one late verdict during an observation interval. The concurrent pane case proves attribution, not server-side decision.answer arbitration; that method does not exist yet.

Use the quickstart to repeat the native allow/deny experiment.