Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions .github/scripts/intake-triage-legs.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ test("every model job tells its launcher whether the fallback token is set, neve
}
});

test("the fallback job runs on a fixed GitHub-hosted label only after the subscription job hands off", () => {
test("the fallback job runs on a fixed GitHub-hosted label only after the subscription job hands off or finds its token empty with the fallback token set", () => {
assert.equal(fallback.name, "claude-intake-triage-fallback");
assert.equal(fallback["runs-on"], "ubuntu-24.04");
assert.deepEqual(fallback.needs, ["claude", "subscription"]);
Expand All @@ -132,7 +132,24 @@ test("the fallback job runs on a fixed GitHub-hosted label only after the subscr
JSON.stringify(handoff),
);
}
// An empty subscription token is not a handoff: no fallback.
// An empty subscription token hands on as an empty key does, but only to
// a fallback token that is set (claude-code-plugins#13586).
assert.equal(
starts({
handoff: "",
"credential-present": "false",
"fallback-ready": "true",
}),
true,
);
assert.equal(
starts({
handoff: "",
"credential-present": "false",
"fallback-ready": "false",
}),
false,
);
assert.equal(starts({ handoff: "", "credential-present": "false" }), false);
assert.doesNotMatch(fallback.if, /always\(\)|failure\(\)|cancelled\(\)/u);
for (const step of fallback.steps) {
Expand All @@ -149,7 +166,8 @@ test("the fallback job runs on a fixed GitHub-hosted label only after the subscr
const FALLBACK_LEG_REASON =
`\${{ needs.subscription.outputs.handoff-reason == 'limit-stop' && 'limit-stop' || ` +
"needs.subscription.outputs.handoff-reason == 'credit-stop' && 'credit-stop' || " +
"needs.subscription.outputs.handoff-reason == 'auth' && 'auth' || '' }}";
"needs.subscription.outputs.handoff-reason == 'auth' && 'auth' || " +
"needs.subscription.outputs.credential-present == 'false' && 'no-key' || '' }}";

test("the fallback job repeats the subscription job's steps but for the credential and the fallback reason", () => {
const strip = (steps, secret, input) =>
Expand Down Expand Up @@ -189,6 +207,16 @@ test("the fallback job repeats the subscription job's steps but for the credenti
});
for (const known of ["limit-stop", "credit-stop", "auth"])
assert.equal(reason(known), known);
assert.equal(
evaluate(value, {
needs: {
subscription: {
outputs: { "handoff-reason": "", "credential-present": "false" },
},
},
}),
"no-key",
);
for (const forged of ["", "no-key", "Limit-Stop", "limit-stop\n::error::x"]) {
assert.equal(reason(forged), "", JSON.stringify(forged));
}
Expand Down
25 changes: 24 additions & 1 deletion .github/scripts/pr-review-cli.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,28 @@ test("the fallback job starts only after the subscription job succeeded and hand
for (const result of ["failure", "cancelled", "skipped"]) {
assert.equal(starts({ handoff: "true" }, result), false, result);
}
// An empty subscription token hands on as an empty key does, but only to a
// fallback token that is set (claude-code-plugins#13586).
assert.equal(
starts({
handoff: "",
"credential-present": "false",
"fallback-ready": "true",
}),
true,
);
assert.equal(
starts({
handoff: "",
"credential-present": "false",
"fallback-ready": "false",
}),
false,
);
assert.equal(
starts({ handoff: "", "credential-present": "", "fallback-ready": "true" }),
false,
);
// A fork, a draft or Dependabot never reaches it either.
assert.equal(
Boolean(
Expand All @@ -198,7 +220,8 @@ test("the fallback job starts only after the subscription job succeeded and hand
const FALLBACK_LEG_REASON =
`\${{ needs.review-subscription.outputs.handoff-reason == 'limit-stop' && 'limit-stop' || ` +
"needs.review-subscription.outputs.handoff-reason == 'credit-stop' && 'credit-stop' || " +
"needs.review-subscription.outputs.handoff-reason == 'auth' && 'auth' || '' }}";
"needs.review-subscription.outputs.handoff-reason == 'auth' && 'auth' || " +
"needs.review-subscription.outputs.credential-present == 'false' && 'no-key' || '' }}";

test("the fallback job repeats the subscription job's steps but for the credential and the fallback reason", () => {
const strip = (steps, secret, input) =>
Expand Down
9 changes: 7 additions & 2 deletions .github/workflows/intake-triage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ jobs:
handoff-reason: ${{ steps.claude.outputs.handoff-reason }}
credential: ${{ steps.claude.outputs.credential }}
credential-present: ${{ steps.credential.outputs.present }}
fallback-ready: ${{ steps.credential.outputs.fallback-ready }}
triage-failed: ${{ steps.review-outcome.outputs.review-failed }}
failure-class: ${{ steps.claude.outputs.lane-class || steps.review-outcome.outputs.failure-class }}
result-subtype: ${{ steps.extract.outputs.result-subtype }}
Expand Down Expand Up @@ -1132,6 +1133,7 @@ jobs:
handoff-reason: ${{ steps.claude.outputs.handoff-reason }}
credential: ${{ steps.claude.outputs.credential }}
credential-present: ${{ steps.credential.outputs.present }}
fallback-ready: ${{ steps.credential.outputs.fallback-ready }}
triage-failed: ${{ steps.review-outcome.outputs.review-failed }}
failure-class: ${{ steps.claude.outputs.lane-class || steps.review-outcome.outputs.failure-class }}
result-subtype: ${{ steps.extract.outputs.result-subtype }}
Expand Down Expand Up @@ -1870,7 +1872,9 @@ jobs:
fallback:
name: claude-intake-triage-fallback
needs: [claude, subscription]
if: needs.subscription.outputs.handoff == 'true'
if: >-
needs.subscription.outputs.handoff == 'true'
|| needs.subscription.outputs.credential-present == 'false' && needs.subscription.outputs.fallback-ready == 'true'
runs-on: ubuntu-24.04
timeout-minutes: 19
# The model's job reads and never writes: the token can read the issue
Expand All @@ -1885,6 +1889,7 @@ jobs:
handoff-reason: ${{ steps.claude.outputs.handoff-reason }}
credential: ${{ steps.claude.outputs.credential }}
credential-present: ${{ steps.credential.outputs.present }}
fallback-ready: ${{ steps.credential.outputs.fallback-ready }}
triage-failed: ${{ steps.review-outcome.outputs.review-failed }}
failure-class: ${{ steps.claude.outputs.lane-class || steps.review-outcome.outputs.failure-class }}
result-subtype: ${{ steps.extract.outputs.result-subtype }}
Expand Down Expand Up @@ -2495,7 +2500,7 @@ jobs:
known-failure-class: ${{ steps.claude.outputs.lane-class }}
handoff: ${{ steps.claude.outputs.handoff }}
# Why this job ran, for this leg's lane record.
fallback-reason: ${{ needs.subscription.outputs.handoff-reason == 'limit-stop' && 'limit-stop' || needs.subscription.outputs.handoff-reason == 'credit-stop' && 'credit-stop' || needs.subscription.outputs.handoff-reason == 'auth' && 'auth' || '' }}
fallback-reason: ${{ needs.subscription.outputs.handoff-reason == 'limit-stop' && 'limit-stop' || needs.subscription.outputs.handoff-reason == 'credit-stop' && 'credit-stop' || needs.subscription.outputs.handoff-reason == 'auth' && 'auth' || needs.subscription.outputs.credential-present == 'false' && 'no-key' || '' }}

# After the outcome step: its timeout check reads the clock when it
# runs, so time spent here must not count against a failed leg.
Expand Down
9 changes: 7 additions & 2 deletions .github/workflows/pr-review-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,7 @@ jobs:
handoff-reason: ${{ steps.claude-4.outputs.handoff-reason || steps.claude-3.outputs.handoff-reason || steps.claude-2.outputs.handoff-reason || steps.claude.outputs.handoff-reason }}
credential: ${{ steps.claude.outputs.credential }}
credential-present: ${{ steps.credential.outputs.present }}
fallback-ready: ${{ steps.credential.outputs.fallback-ready }}
review-failed: ${{ steps.review-outcome.outputs.review-failed }}
failure-class: ${{ steps.claude-4.outputs.lane-class || steps.claude-3.outputs.lane-class || steps.claude-2.outputs.lane-class || steps.claude.outputs.lane-class || steps.review-outcome.outputs.failure-class || ((steps.isolate.outcome == 'failure' || steps.isolate.outcome == 'cancelled') && 'install-failed') || '' }}
verdict: ${{ steps.extract.outputs.verdict }}
Expand Down Expand Up @@ -2620,6 +2621,7 @@ jobs:
handoff-reason: ${{ steps.claude-4.outputs.handoff-reason || steps.claude-3.outputs.handoff-reason || steps.claude-2.outputs.handoff-reason || steps.claude.outputs.handoff-reason }}
credential: ${{ steps.claude.outputs.credential }}
credential-present: ${{ steps.credential.outputs.present }}
fallback-ready: ${{ steps.credential.outputs.fallback-ready }}
review-failed: ${{ steps.review-outcome.outputs.review-failed }}
failure-class: ${{ steps.claude-4.outputs.lane-class || steps.claude-3.outputs.lane-class || steps.claude-2.outputs.lane-class || steps.claude.outputs.lane-class || steps.review-outcome.outputs.failure-class || ((steps.isolate.outcome == 'failure' || steps.isolate.outcome == 'cancelled') && 'install-failed') || '' }}
verdict: ${{ steps.extract.outputs.verdict }}
Expand Down Expand Up @@ -4683,7 +4685,9 @@ jobs:
timeout-minutes: ${{ inputs.lane == 'security' && 117 || 109 }}
if: >-
needs.review-subscription.result == 'success'
&& needs.review-subscription.outputs.handoff == 'true'
&& (needs.review-subscription.outputs.handoff == 'true'
|| needs.review-subscription.outputs.credential-present == 'false'
&& needs.review-subscription.outputs.fallback-ready == 'true')
&& !cancelled()
&&
github.event_name == 'pull_request'
Expand All @@ -4710,6 +4714,7 @@ jobs:
handoff-reason: ${{ steps.claude-4.outputs.handoff-reason || steps.claude-3.outputs.handoff-reason || steps.claude-2.outputs.handoff-reason || steps.claude.outputs.handoff-reason }}
credential: ${{ steps.claude.outputs.credential }}
credential-present: ${{ steps.credential.outputs.present }}
fallback-ready: ${{ steps.credential.outputs.fallback-ready }}
review-failed: ${{ steps.review-outcome.outputs.review-failed }}
failure-class: ${{ steps.claude-4.outputs.lane-class || steps.claude-3.outputs.lane-class || steps.claude-2.outputs.lane-class || steps.claude.outputs.lane-class || steps.review-outcome.outputs.failure-class || ((steps.isolate.outcome == 'failure' || steps.isolate.outcome == 'cancelled') && 'install-failed') || '' }}
verdict: ${{ steps.extract.outputs.verdict }}
Expand Down Expand Up @@ -6699,7 +6704,7 @@ jobs:
known-failure-class: ${{ steps.claude-4.outputs.lane-class || steps.claude-3.outputs.lane-class || steps.claude-2.outputs.lane-class || steps.claude.outputs.lane-class }}
handoff: ${{ steps.claude-4.outputs.handoff || steps.claude-3.outputs.handoff || steps.claude-2.outputs.handoff || steps.claude.outputs.handoff }}
# Why this job ran, for this leg's lane record.
fallback-reason: ${{ needs.review-subscription.outputs.handoff-reason == 'limit-stop' && 'limit-stop' || needs.review-subscription.outputs.handoff-reason == 'credit-stop' && 'credit-stop' || needs.review-subscription.outputs.handoff-reason == 'auth' && 'auth' || '' }}
fallback-reason: ${{ needs.review-subscription.outputs.handoff-reason == 'limit-stop' && 'limit-stop' || needs.review-subscription.outputs.handoff-reason == 'credit-stop' && 'credit-stop' || needs.review-subscription.outputs.handoff-reason == 'auth' && 'auth' || needs.review-subscription.outputs.credential-present == 'false' && 'no-key' || '' }}

# After the outcome step: its timeout check reads the clock when it
# runs, so time spent here must not count against a failed leg.
Expand Down
Loading