feat(proxy): wire interactive approval into proxy and proxy-http#183
Merged
Conversation
Make `ask` decisions usable in both live proxies instead of the hardwired deny-all behavior, and unify the approver contract the three call sites share. - #137: collapse the duplicate proxy.Approver / httpproxy.Approver interfaces and their DenyAllApprover structs into type aliases for the single approval.Approver / approval.DenyAllApprover (no import cycle). - #126: connect approval.TTYApprover and a new --approval-timeout to runProxy and runProxyHTTP; --no-interactive keeps the fail-closed DenyAllApprover. Add approval.Resolve as the shared ask->decision mapping (check's resolveAsk now delegates to it). The proxies audit the resolved decision with the approval outcome appended to the engine reason (preserving taint-escalation context) and return the canonical reason to the agent without leaking internal approver error text. - #129: table-driven regression tests across both proxies for approve, deny, timeout, I/O error, non-interactive, and ask-on-notification. - #127: correct docs/integration-guide.md and docs/architecture.md that still described proxy-http and the approver as roadmap/unimplemented and referenced closed issues #29/#30. https://claude.ai/code/session_01K8hWMifivsN6RMKqzXV3gX
There was a problem hiding this comment.
Pull request overview
This PR wires interactive ask approval into both live proxies (stdio and HTTP) by unifying the Approver contract across packages and centralizing ask→decision reason mapping in internal/approval. It also updates docs/changelog and adds regression coverage to lock the behavior in.
Changes:
- Aliases
proxy.Approver/httpproxy.Approver(and deny-all implementations) to the sharedapproval.Approvercontract. - Adds
approval.Resolveand uses it incheck,proxy, andproxy-httpto apply approval timeout + consistent canonical reasons. - Expands proxy/httpproxy tests and updates documentation to reflect shipped interactive approval behavior.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/proxy/proxy.go | Uses shared approver + resolves ask before auditing/forwarding; adds timeout/non-interactive options. |
| internal/proxy/proxy_test.go | Adds/updates regression tests for approve/deny/error/timeout/non-interactive/notification behavior. |
| internal/httpproxy/httpproxy.go | Mirrors stdio proxy: resolves ask via shared resolver before audit/response/forward. |
| internal/httpproxy/httpproxy_test.go | Adds approval-path tests for allow/deny/error/timeout and audit assertions. |
| internal/approval/approval.go | Introduces Outcome + Resolve as canonical ask→reason mapping used by all call sites. |
| cmd/agentfence/main.go | Wires TTY approver + --approval-timeout into proxy and proxy-http; delegates check ask resolution to approval.Resolve. |
| docs/integration-guide.md | Updates approval guidance and removes stale “unimplemented/roadmap” statements. |
| docs/architecture.md | Updates architecture description to match interactive approval + timeout behavior. |
| CHANGELOG.md | Documents the new proxy approval behavior, unified approver contract, and reason/audit behavior changes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Addresses PR review: newProxyApprover used approval.NewTTYApprover(), which silently falls back to os.Stdin when /dev/tty is unavailable. In the stdio proxy, stdin is the agent's JSON-RPC channel, so that fallback could block on protocol traffic or interleave prompt reads with frames. - Add approval.NewTTYApproverStrict() that opens /dev/tty and errors instead of falling back to stdin; NewTTYApprover() (used by check) keeps the fallback. - newProxyApprover now uses the strict constructor and, when no terminal is present, fails with a message pointing the operator to --no-interactive. - Document the strict behavior in the integration guide. - Add cmd tests covering the deny-all and require-TTY paths. https://claude.ai/code/session_01K8hWMifivsN6RMKqzXV3gX
Owner
Author
|
Good catch — fixed in e5918b4. Both comments are the same root cause: the proxies must not fall back to stdin for prompts.
Generated by Claude Code |
…triage-wqfk36 # Conflicts: # cmd/agentfence/main.go # cmd/agentfence/main_test.go # internal/httpproxy/httpproxy.go
When an ask prompt times out or is cancelled, TTYApprover.Request returned while its read goroutine stayed parked on /dev/tty. With --approval-timeout now wired into the long-running proxies, repeated timeouts could leave multiple goroutines racing on the same fd, letting a stale reader steal or drop the operator's keystroke. Keep a single persistent bufio.Reader and reuse the in-flight read across calls so exactly one reader exists and a late keystroke is delivered to the next prompt. Also guard the audit-reason annotation against an empty engine reason to avoid a leading-space '(reason)' string. Adds a regression test covering the timeout-then-late-response path.
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.
Summary
Makes
askdecisions usable in both live proxies instead of the hardwired deny-all behavior, and unifies the approver contract the three call sites share. This delivers a coherent group of four related issues that all live in the approval/proxy code path.What changed:
Approverinterfaces behind one shared contract #137 — unify theApprovercontract.proxy.Approver/httpproxy.Approverand theirDenyAllApproverstructs are now type aliases for the singleapproval.Approver/approval.DenyAllApprover, so one approver implementation wires into both proxies andcheck(no adapters, no import cycle).proxyandproxy-http#126 — wire the TTY approver + timeout into the proxies.runProxy/runProxyHTTPnow build a realapproval.TTYApproverby default and accept a new--approval-timeout;--no-interactivekeeps the fail-closedDenyAllApprover. A new sharedapproval.Resolveis the single ask→decision mapping (the CLI'sresolveAsknow delegates to it). The proxies audit the resolved decision with the approval outcome appended to the engine's reason (preserving taint-escalation context) and return the canonical reason to the agent without leaking internal approver error text.askapproval paths #129 — regression tests. Table-driven coverage across both proxies for approve, deny, timeout, I/O error, non-interactive, and ask-on-notification, asserting both the agent-facing response and the audit decision/reason.docs/integration-guide.mdanddocs/architecture.mdno longer describeproxy-http/the interactive approver as roadmap/unimplemented or reference closed issues [Approval] Implement interactive TTY approval for ask decisions #29/[Approval] Add approval timeout with default-deny fallback #30.Related issue
Closes #126
Closes #127
Closes #129
Closes #137
How verified
make ci(fmt-check,go vet ./...,go test -race ./...) — all packages pass, race-clean, total coverage 80.1%.internal/proxy(TestProcessAgentLineAskApproved/Denied/Errors/Timeout/NonInteractive/NotificationDropped) andinternal/httpproxy(TestHTTPAskApprovedForwards/ApproverErrorBlocks/Timeout+ reason assertion on the existing deny test) — each fails without the fix (deny-all) and passes with it.agentfence proxy --helplists--approval-timeout; an invalid--approval-timeoutvalue is rejected with a parse error.Tradeoffs / risks
ask; they now audit the resolvedallow/deny. The agent-facing blocked message now uses canonical reasons (denied interactively,approval timeout,approval I/O error) instead of"... (denied via ask)"/"approval error: <raw>", and the raw approver error text is no longer exposed to the agent. Documented in the CHANGELOG./dev/tty; where that is unavailable (e.g. Windows/CI) interactive approval isn't meaningful — use--no-interactivethere. The CLI'sresolveAskreason text is unchanged (an existing test pinsnon-interactive: ask auto-denied).Checklist
https://claude.ai/code/session_01K8hWMifivsN6RMKqzXV3gX
Generated by Claude Code