Skip to content

fix: wire GH_AW_AIC and GH_AW_MAX_AI_CREDITS to Handle agent failure step#38327

Merged
pelikhan merged 1 commit into
mainfrom
copilot/investigate-ai-credits-issue
Jun 10, 2026
Merged

fix: wire GH_AW_AIC and GH_AW_MAX_AI_CREDITS to Handle agent failure step#38327
pelikhan merged 1 commit into
mainfrom
copilot/investigate-ai-credits-issue

Conversation

Copilot AI commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Handle agent failure received GH_AW_AI_CREDITS_RATE_LIMIT_ERROR=true but never the actual credit values, so failure issues showed "AI Credits Budget Exceeded" with no usage or limit data. The noop step already had GH_AW_AIC; the failure step did not.

Root cause

resolveAICreditsFailureState() falls back to process.env.GH_AW_AIC / process.env.GH_AW_MAX_AI_CREDITS, but neither was in the step's env: block. shouldReportAICreditsRateLimitError(true, "", "") conservatively returns true when data is absent — correct behavior, wrong inputs.

Changes

  • pkg/workflow/notify_comment.go — adds two env vars to agentFailureEnvVars:

    • GH_AW_AIC: ${{ needs.agent.outputs.aic }} — credits consumed
    • GH_AW_MAX_AI_CREDITS — compile-time literal when max-ai-credits is in frontmatter; ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} otherwise (mirrors the firewall config logic)
  • actions/setup/js/ai_credits_context.cjs — adds provenance logging to resolveAICreditsFailureState(), reporting the source of each resolved value (audit_log / agent_stdio / env(GH_AW_AIC) / none) to the step log

  • 245 lock files recompiled

…step, add provenance logging

- `Handle agent failure` step was missing `GH_AW_AIC` (credits used) and `GH_AW_MAX_AI_CREDITS`
  (configured limit), causing "AI Credits Budget Exceeded" issues to show no data
- Wire both env vars in `notify_comment.go` agentFailureEnvVars: AIC from agent job output,
  max from compile-time literal (when set in frontmatter) or runtime vars expression
- Add provenance logging to `resolveAICreditsFailureState()` so each value's source
  (audit_log / agent_stdio / env / none) is visible in the step log

Fixes #38322 and #38324

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI requested a review from pelikhan June 10, 2026 10:34
@pelikhan pelikhan marked this pull request as ready for review June 10, 2026 10:43
Copilot AI review requested due to automatic review settings June 10, 2026 10:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes missing AI Credits usage/limit data in the Handle agent failure step by wiring the relevant env vars through to the failure handler and adding step-log provenance to aid debugging when credit data is absent.

Changes:

  • Pass GH_AW_AIC and GH_AW_MAX_AI_CREDITS into the agent-failure handler env so failure issues can include credits used + configured limit.
  • Add provenance logging in resolveAICreditsFailureState() to show which source (audit log / agent stdio / env / none) provided AI credits values.
  • Regenerate compiled workflow .lock.yml files to reflect the new env wiring.
Show a summary per file
File Description
pkg/workflow/notify_comment.go Threads AI credits usage (GH_AW_AIC) and budget limit (GH_AW_MAX_AI_CREDITS) into the agent-failure handler step env.
actions/setup/js/ai_credits_context.cjs Logs provenance for resolved AI credits values during failure handling.
.github/workflows/*.lock.yml Regenerated compiled workflows to include the new env vars in Handle agent failure.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comments suppressed due to low confidence (1)

actions/setup/js/ai_credits_context.cjs:315

  • max-ai-credits supports the sentinel value -1 (disable enforcement), but this resolver only parses positive numbers. If GH_AW_MAX_AI_CREDITS is "-1" (from frontmatter or vars.GH_AW_DEFAULT_MAX_AI_CREDITS), maxAICredits becomes empty and shouldReportAICreditsRateLimitError will still conservatively flag an AI-credits budget failure; the failure handler will also log the configured max as "(none)". Consider treating "-1" as an explicit "no limit" value and forcing aiCreditsRateLimitError to false in that case.
  • Files reviewed: 247/247 changed files
  • Comments generated: 0

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR #38327 does not have the implementation label (has_implementation_label=false) and adds only 13 lines in business logic directories, well below the 100-line threshold (requires_adr_by_default_volume=false).

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel completed test quality analysis.

No test files were added or modified in this PR. Test Quality Sentinel skipped.

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions github-actions Bot mentioned this pull request Jun 10, 2026
@pelikhan pelikhan merged commit 2446604 into main Jun 10, 2026
104 of 107 checks passed
@pelikhan pelikhan deleted the copilot/investigate-ai-credits-issue branch June 10, 2026 11:04

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REQUEST_CHANGES — one correctness bug must be fixed before merge

The core wiring is correct: GH_AW_AIC and GH_AW_MAX_AI_CREDITS were genuinely missing from the failure handler env block, and the provenance logging adds useful diagnostic signal. Three issues need attention:

### Blocking: wrong comparator lets the `-1` (unlimited) sentinel produce broken YAML

MaxAICredits != 0 is true for -1, which is the valid spec-supported unlimited sentinel (max-ai-credits: -1). The literal branch then emits GH_AW_MAX_AI_CREDITS: "-1", which parsePositiveNumberString rejects — leaving maxAICredits empty in the failure comment and the provenance log saying source=none while the var is clearly set. Fix: > 0 instead of != 0, and drop the dead data != nil conjunct while there. See inline comment.

### Non-blocking: missing tests and unreadable ternary
  • The new GH_AW_AIC / GH_AW_MAX_AI_CREDITS env-var wiring has no test coverage. The two-branch branching logic is the most failure-prone part and is entirely unasserted. See inline comment.
  • The rawRateLimitSignalSource ternary on line 309 of ai_credits_context.cjs is 200+ characters in a function that uses clean if/else if blocks for every other signal. See inline comment.

🔎 Code quality review by PR Code Quality Reviewer · ⌖ 13.5 AIC

agentFailureEnvVars = append(agentFailureEnvVars, fmt.Sprintf(" GH_AW_EFFECTIVE_TOKENS: ${{ needs.%s.outputs.effective_tokens || '' }}\n", mainJobName))
agentFailureEnvVars = append(agentFailureEnvVars, fmt.Sprintf(" GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.%s.outputs.ai_credits_rate_limit_error || 'false' }}\n", mainJobName))
agentFailureEnvVars = append(agentFailureEnvVars, fmt.Sprintf(" GH_AW_AIC: ${{ needs.%s.outputs.aic }}\n", mainJobName))
if data != nil && data.EngineConfig != nil && data.EngineConfig.MaxAICredits != 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong comparator != 0 will silently discard the -1 (unlimited) sentinel and mislead the failure handler. When max-ai-credits: -1 is set (explicitly supported by the spec to disable budget enforcement), MaxAICredits != 0 is true, so the literal branch fires and emits GH_AW_MAX_AI_CREDITS: "-1" in the YAML. On the JS side, parsePositiveNumberString("-1") returns "" (it requires value > 0), so maxAICredits resolves to empty string and the provenance log records source=none GH_AW_MAX_AI_CREDITS=-1 — a confusing contradiction. The condition should be > 0.

💡 Suggested fix
// Before
if data != nil && data.EngineConfig != nil && data.EngineConfig.MaxAICredits != 0 {

// After
if data.EngineConfig != nil && data.EngineConfig.MaxAICredits > 0 {

(Note: the data != nil guard can be dropped — data is unconditionally dereferenced many lines earlier in this function, e.g. at data.SafeOutputs on line 33, so it cannot be nil here. The extra conjunct is dead code that misleads readers into believing a nil-data code path exists.)

console.log(`[ai-credits] maxAICredits source=none GH_AW_MAX_AI_CREDITS=${process.env.GH_AW_MAX_AI_CREDITS || "(unset)"}`);
}

const rawRateLimitSignalSource = auditRateLimitError ? "audit_log" : stdioSignals.rateLimitError ? "agent_stdio" : process.env.GH_AW_AI_CREDITS_RATE_LIMIT_ERROR === "true" ? "env(GH_AW_AI_CREDITS_RATE_LIMIT_ERROR)" : "none";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

200+ character single-line ternary is unreadable and cannot be diffed. This is immediately above two if/else if blocks that handle aiCredits and maxAICredits identically — the inconsistency makes it look like an oversight.

💡 Suggested fix
// Before (one opaque line)
const rawRateLimitSignalSource = auditRateLimitError ? "audit_log" : stdioSignals.rateLimitError ? "agent_stdio" : process.env.GH_AW_AI_CREDITS_RATE_LIMIT_ERROR === "true" ? "env(GH_AW_AI_CREDITS_RATE_LIMIT_ERROR)" : "none";

// After — consistent with the two blocks above it
let rawRateLimitSignalSource;
if (auditRateLimitError) {
  rawRateLimitSignalSource = "audit_log";
} else if (stdioSignals.rateLimitError) {
  rawRateLimitSignalSource = "agent_stdio";
} else if (process.env.GH_AW_AI_CREDITS_RATE_LIMIT_ERROR === "true") {
  rawRateLimitSignalSource = "env(GH_AW_AI_CREDITS_RATE_LIMIT_ERROR)";
} else {
  rawRateLimitSignalSource = "none";
}

agentFailureEnvVars = append(agentFailureEnvVars, fmt.Sprintf(" GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.%s.outputs.ai_credits_rate_limit_error || 'false' }}\n", mainJobName))
agentFailureEnvVars = append(agentFailureEnvVars, fmt.Sprintf(" GH_AW_AIC: ${{ needs.%s.outputs.aic }}\n", mainJobName))
if data != nil && data.EngineConfig != nil && data.EngineConfig.MaxAICredits != 0 {
agentFailureEnvVars = append(agentFailureEnvVars, fmt.Sprintf(" GH_AW_MAX_AI_CREDITS: %q\n", strconv.FormatInt(data.EngineConfig.MaxAICredits, 10)))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No test asserts GH_AW_AIC or GH_AW_MAX_AI_CREDITS appear in the conclusion job env block. The two-branch logic (MaxAICredits > 0 literal vs. expression fallback) is completely uncovered. A regression that drops either line, misspells the key, or inverts the branch condition would pass CI undetected.

💡 What to test

Two new test cases in notify_comment_test.go:

  1. Literal path — set EngineConfig.MaxAICredits = 500, call buildConclusionJob, assert the output contains GH_AW_MAX_AI_CREDITS: "500" and GH_AW_AIC: ${{ needs.agent.outputs.aic }}.

  2. Expression path — leave EngineConfig.MaxAICredits = 0 (or EngineConfig = nil), assert the output contains GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS.

  3. Unlimited sentinel (once the != 0> 0 fix is applied) — set MaxAICredits = -1, assert it falls through to the expression path rather than emitting "-1".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants