fix(acp): Constrain permission option kinds to the ACP schema - #358
Merged
shayne-snap merged 1 commit intoAug 10, 2026
Merged
Conversation
PermissionOption.Kind was a free-form string, so nothing at compile time or runtime prevented sending a kind outside the ACP schema. The schema enum (agent-client-protocol-schema) defines exactly four canonical kinds: allow_once | allow_always | reject_once | reject_always. v1 rejects any other kind at deserialization — the observed failure: Zed's strict serde refused allow_tool/allow_server, the dialog never produced a selected outcome, and the approval was silently denied (whale-acp logs `unknown permission outcome: ""`). v2 tolerates unknown kinds via an untagged Other(String) fallback, but the dialog's option lookup never matches Other, so those kinds still cannot work — and would become a future hazard if whale ever negotiates v2. Close the systemic hole so only schema-valid kinds can reach the wire: - types.go: Kind string -> typed PermissionOptionKind + constants KindAllowOnce/KindAllowAlways/KindRejectOnce/KindRejectAlways + Valid() + String() - adapter.go: NewACPApprovalFunc denies loudly via invalidPermissionOptionKind instead of sending a payload the client would reject - tests: serialize the real request_permission payload for every tool-name permutation and assert every kind is schema-valid; cross-check against a strict decoder mirroring the serde enum; cover the decision-mapping downstream, error envelope, transport close, and 32 concurrent sessions (-race clean) Co-authored-by: GPT-5.6 Sol <codex@openai.com>
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
PermissionOption.Kindwas a free-form string, so nothing at compile time or runtime prevented sending a kind outside the ACP schema. The schema enum (agent-client-protocol-schema) defines exactly four canonical kinds:allow_once | allow_always | reject_once | reject_always.allow_tool/allow_server, the dialog never produced aselectedoutcome, and the approval was silently denied (unknown permission outcome: "").Other(String)fallback, but the dialog's option lookup never matchesOther, so those kinds still cannot work — and would become a future hazard if whale ever negotiates v2.Changes
types.go):Kind string→PermissionOptionKind+ constantsKindAllowOnce/KindAllowAlways/KindRejectOnce/KindRejectAlways+Valid()+String(), matching the schema enum.adapter.go):NewACPApprovalFuncdenies loudly viainvalidPermissionOptionKindinstead of sending a payload the client would reject.once/always/rejectoptions now use the typed constants (wire values unchanged).Validation
go build ./...,go vet, gofmt clean.-raceoninternal/acp.request_permissionpayload for every tool-name permutation and assert every kind is schema-valid; cross-check against a strict decoder mirroring the serde enum;Valid()guard;invalidPermissionOptionKindhelper (nil, first-invalid, valid); decision-mapping downstream (once/always/reject/unknown/missing/cancelled/unknown-outcome/malformed); error envelope; transport close; 32 concurrent sessions (-race clean).Review guidance
types.goPermissionOptionKind+Valid(): the four canonical kinds are the single source of truth; any new kind must first exist in the schema crate (v2'sOther(String)fallback is an escape hatch, not a license to invent kinds).adapter.goguard +invalidPermissionOptionKind: fail-loud deny before the wire, replacing the old silent denial.User-visible impact
approval_denied, turn canceled) when the client rejects unserializable kinds — that failure can kill a session.Breaking changes
None. Wire values unchanged (
allow_once/allow_always/reject_once); typed field marshals identically to the previous string.Reproduction
request_permissionwithkind: "allow_tool"→ Zed.logunknown variant 'allow_tool', expected one of 'allow_once', 'allow_always', 'reject_once', 'reject_always'→unknown permission outcome: "" — denying.TestPermissionOptionKindsAreSchemaValidserializes every option permutation and asserts schema-valid kinds on the wire.Developed with carefully directed, manually reviewed AI assistance.