Repository navigation
fix(approvals): tell an expired approval apart from a rejection - #674
Eunyoung Jeong (eunyoung14) wants to merge 3 commits into
Conversation
Co-Authored-By: Codex <noreply@openai.com>
…provals lookup Co-Authored-By: Codex <noreply@openai.com>
There was a problem hiding this comment.
🟡 Changes recommended
The deadline message is ambiguous because it omits the local date and timezone, making demands that cross midnight or are read later difficult to interpret.
1 open issue
What changed in this PR
This PR improves approval and purpose-demand diagnostics for alpacon exec, distinguishing expired approvals from reviewer rejections and reporting where commands land after a purpose demand expires.
Changes:
- Propagates rejection causes and stable JSON
error_codevalues across execution, approval waits, andexec logs. - Adds approval-request lookup and purpose-demand outcome reporting to command logs.
- Expands deadline guidance, help text, and regression coverage for approval and purpose-demand flows.
| File | Summary | Review notes |
|---|---|---|
utils/purpose_demand.go |
Adds deadline and post-expiry guidance to purpose-demand messages. | Moderate: Include the local date and timezone in the deadline, not only the time, so deadlines crossing midnight remain unambiguous. |
utils/purpose_demand_test.go |
Tests deadline and lapse guidance in table and JSON output. | — |
cmd/worksession/worksession_create.go |
Documents session-purpose behavior. | — |
cmd/exec/use_file_execution_test.go |
Covers cause-specific messages and JSON codes across execution paths. | — |
cmd/exec/run.go |
Preserves rejection causes through the approval wait loop. | Moderate: Preserve the legacy expired outcome when sudo_grant_status=expired is returned without a rejection cause, while treating cause-bearing rejected responses as terminal. |
cmd/exec/run_test.go |
Updates approval wait message expectations. | — |
cmd/exec/purpose_demand_test.go |
Tests purpose-demand lapse outcomes and approval lookup output. | — |
cmd/exec/pending_approval_test.go |
Tests approval lookup fallback behavior. | — |
cmd/exec/logs.go |
Reports purpose-demand outcomes and approval request IDs. | — |
cmd/exec/logs_test.go |
Tests completion reporting after purpose demands. | — |
cmd/exec/exec.go |
Updates exec help for purpose-demand expiry behavior. |
Nit: Update the related purpose help and README exit-code documentation, which still describe the old post-expiry behavior. |
api/event/types.go |
Adds approval cause constants, messages, and error-code mapping. | Moderate: Emit approval_expired only when a cause is present so legacy expiry responses retain the not_approved JSON fallback. |
api/event/event_test.go |
Tests rejection-cause messages and error-code mapping. | — |
api/approval/types.go |
Adds command ID decoding to approval metadata. | Moderate: Decode and match the nested metadata.command.command_id shape used by command approvals, while retaining a fallback for other server shapes. |
api/approval/approval.go |
Adds paginated lookup of pending command approval requests. | — |
api/approval/approval_test.go |
Tests approval lookup pagination and early termination. | — |
Review effort: Lite
Match nested command approval metadata with the existing flat fallback. Preserve a cause-less expired grant when the command is also rejected. Co-Authored-By: Codex <noreply@openai.com>
|
Review-body findings addressed in d5e8e70:
Fixed at
Fixed at
Both regressions failed before the fixes. The build, vet, full race-test suite, and lint all pass. Applied the two logic fixes with regression tests and retained frozen wording and code mappings; the fix commit can be reverted independently. |
There was a problem hiding this comment.
WALL-D review: Approval withheld · head d5e8e70
Code review result: Comments only (1 P1/P2) · head d5e8e70
Review summary
- Found 1 P1/P2 defect.
A maintainer will review this PR.
Related PRs
- Companion PRs: #674, alpacax/alpacon-mcp#436 (grouped by: branch)
Product review result: Check needed (2 items) · head d5e8e70
A maintainer will follow up on the product items.
Re-review requests and limits
WALL-D reviews a PR opened ready after this rule took effect once on its own. After that it reviews only on request: comment @alpacax-wall-d review or mark a draft ready for review, up to 5 times per PR including the first review. A re-review reads only the lines changed since the last review, and from the second re-review on only P1 findings block approval. Answering the product review's questions reviews only the product side again.
| if e.Expired { | ||
| return RejectionCauseApprovalExpired | ||
| } |
There was a problem hiding this comment.
P2 · contract Legacy expiries without a rejection cause no longer preserve the fallback error code.
Failure scenario: An approval poll receives sudo_grant_status=expired without rejection_cause → ErrorCode returns approval_expired through Expired, while the equivalent cause-free exec and exec logs paths retain not_approved.
There was a problem hiding this comment.
Intended, so no code change: on the exec --wait sudo-grant poll the client learns the expiry itself from sudo_grant_status=expired, and api/event/types.go:339 reports approval_expired for it. The cause-free exec and exec logs paths keep not_approved because there the client can't tell an expiry from a reviewer's rejection. In both cases the code reflects what the client can observe, and an expiry it knows about is never reported as a rejection. The full answer is in the PR comment.
|
Answers to the WALL-D review on P2, Product item, release notes: this ships as a fix in the next CLI release notes. Expired approvals no longer read as a reviewer's rejection, Product item, documentation follow-up: planned, not deferred. The public CLI reference for @alpacax-wall-d review |
|
No changes in d5e8e70..d5e8e70. |

An agent running commands through
alpacon execcouldn't tell a timed-out approval from a person saying no. A lapsed purpose demand also didn't say where the command went.Changes
rejection_cause=approval_expirednow reads "was not approved before the approval request expired" on every command path:exec,exec --wait(the poll loop now keeps the cause and treatsstatus=rejectedas terminal) andexec logs. A human rejection keeps "was rejected by a reviewer". An older server that sends no cause keeps today's wording.--output jsonenvelope carries a stableerror_codeper cause:approval_expired,approval_rejected, and the other causes the server sends. It's the same on all three paths. Older servers keepnot_approved.alpacon exec logs <id>.exec logs <id>on a command that went through a purpose demand reports where it landed: running or finished, awaiting approval (with the approval request id), or refused (with the cause). The request id lookup stays quiet on 403, 404 or an older server, and stops paging at the first match. Output for an ordinary command is unchanged.execandwork-session create: the session purpose is what each command is judged against. Pass--purposewith each command to skip the demand.The 60-second purpose window and the server state model are unchanged.
Tests
error_code, and older-server fallbacks.exec logsnotice, shown only after a purpose demand.The regression tests fail without the change.
go build,go vet,go test -race ./...andgolangci-lint(0 issues) all pass.