Summary
close-issue supports an opt-in issue-intent: true mode that routes the close through the platform's native "intent" API instead of a plain state update, so that automation-control/review policies can gate the change. When that intent request fails, closeIssue() in actions/setup/js/close_issue.cjs catches the error, logs a core.warning, and unconditionally falls back to a plain github.rest.issues.update(...) call with the same state/state_reason. That fallback bypasses whatever policy caused the intent request to fail, and the workflow run still finishes green.
Where
actions/setup/js/close_issue.cjs, closeIssue() — the try { ... } catch (error) { core.warning(...) } block that falls through to github.rest.issues.update(baseParams) on any error, regardless of the error type.
actions/setup/js/issue_intents.cjs, normalizeIssueIntentMetadata() — only extracts rationale, confidence, and suggest from the intent metadata. It never surfaces a resolved canonical-issue identifier for state_reason: duplicate closes, so the intent request for a duplicate close can never include what the API requires to accept it as a duplicate.
Reproduction
- Configure a workflow using
close-issue with state-reason: duplicate and issue-intent: true.
- Have the agent request closing an issue as a duplicate (e.g. via
duplicate_of: <n>), with a repository confidence/review policy configured such that the close must be reviewed rather than applied directly.
- Run the workflow.
Actual behavior
- The intent PATCH request is rejected by the API (HTTP 422: a canonical-issue identifier is required when a close is being routed for review as a duplicate) — because
normalizeIssueIntentMetadata() never sends one.
closeIssue() logs Issue-intent close path unavailable, falling back to legacy close path: ... and immediately calls github.rest.issues.update() with a plain state: "closed", state_reason: "duplicate".
- The issue closes immediately, with no review, no rationale/confidence metadata, and no recorded relationship to the canonical issue.
- The workflow step and run both complete successfully (no failure surfaced).
Expected behavior
- A validation-level failure (4xx) on the intent path must not silently fall back to an unreviewed, un-gated write. The action should fail the item (or otherwise clearly report incomplete/failed) instead of downgrading to a legacy close, so a configured review requirement cannot be bypassed by a client-side error.
- Falling back may still be reasonable for errors that indicate the intent feature/endpoint itself is unavailable (e.g. 404/501), but not for 4xx validation rejections, which represent a policy decision that must be respected, not retried through a different path.
- (Secondary / follow-up)
normalizeIssueIntentMetadata() should resolve duplicate_of to the canonical issue's identifier and include it in the intent metadata when state_reason is duplicate, so a well-formed duplicate-close intent request can succeed in the first place rather than always failing validation.
- (Secondary / follow-up) The
markAsDuplicate GraphQL mutation called elsewhere in the duplicate-close flow currently fails for every run with Field 'markAsDuplicate' doesn't exist on type 'Mutation'. Once a duplicate-close intent request can carry the canonical-issue identifier (item 2), that call may be redundant and worth removing rather than fixed.
Priority
Item 1 (fail instead of silently falling back) is the priority — it's what allows an unreviewed state change to slip through today. Items 2 and 3 are secondary correctness/cleanup follow-ups.
Version
Observed on gh-aw v0.88.8.
Summary
close-issuesupports an opt-inissue-intent: truemode that routes the close through the platform's native "intent" API instead of a plain state update, so that automation-control/review policies can gate the change. When that intent request fails,closeIssue()inactions/setup/js/close_issue.cjscatches the error, logs acore.warning, and unconditionally falls back to a plaingithub.rest.issues.update(...)call with the samestate/state_reason. That fallback bypasses whatever policy caused the intent request to fail, and the workflow run still finishes green.Where
actions/setup/js/close_issue.cjs,closeIssue()— thetry { ... } catch (error) { core.warning(...) }block that falls through togithub.rest.issues.update(baseParams)on any error, regardless of the error type.actions/setup/js/issue_intents.cjs,normalizeIssueIntentMetadata()— only extractsrationale,confidence, andsuggestfrom the intent metadata. It never surfaces a resolved canonical-issue identifier forstate_reason: duplicatecloses, so the intent request for a duplicate close can never include what the API requires to accept it as a duplicate.Reproduction
close-issuewithstate-reason: duplicateandissue-intent: true.duplicate_of: <n>), with a repository confidence/review policy configured such that the close must be reviewed rather than applied directly.Actual behavior
normalizeIssueIntentMetadata()never sends one.closeIssue()logsIssue-intent close path unavailable, falling back to legacy close path: ...and immediately callsgithub.rest.issues.update()with a plainstate: "closed",state_reason: "duplicate".Expected behavior
normalizeIssueIntentMetadata()should resolveduplicate_ofto the canonical issue's identifier and include it in the intent metadata whenstate_reasonisduplicate, so a well-formed duplicate-close intent request can succeed in the first place rather than always failing validation.markAsDuplicateGraphQL mutation called elsewhere in the duplicate-close flow currently fails for every run withField 'markAsDuplicate' doesn't exist on type 'Mutation'. Once a duplicate-close intent request can carry the canonical-issue identifier (item 2), that call may be redundant and worth removing rather than fixed.Priority
Item 1 (fail instead of silently falling back) is the priority — it's what allows an unreviewed state change to slip through today. Items 2 and 3 are secondary correctness/cleanup follow-ups.
Version
Observed on
gh-aw v0.88.8.