Skip to content

ci(pullfrog): retry the review with the fallback subscription token - #40

Open
fabiodalez-dev wants to merge 4 commits into
mainfrom
ci/pullfrog-oauth-fallback
Open

fabiodalez-dev wants to merge 4 commits into
mainfrom
ci/pullfrog-oauth-fallback

Conversation

@fabiodalez-dev

@fabiodalez-dev fabiodalez-dev commented Oct 7, 2026 •

Copy link
Copy Markdown
Owner

When the primary review attempt fails, Pullfrog now makes one additional attempt using PULLFROG_OAUTH_FALLBACK, if configured. A review that cannot run still fails the check. Provider keys remain scoped to the two agent steps. The fallback retains the same provider keys and replaces only the OAuth token; job-level environment contains only its presence flag.

The runtime 0.1.97 credential selector tries the workflow OAuth token before account OAuth tokens of the same kind; its stable subscription sort retains that order. This is verified in the published npm package and its matching credentialPool.ts source. API keys are not claimed to outrank subscriptions. Read-only repository permissions, disabled push, restricted shell and checkout without persisted credentials are retained.

The action exposes result, not a structured failure reason, so the retry applies to any primary failure rather than only 429s. Both attempts are capped at 20 minutes and the job at 45 minutes. A deterministic error may spend fallback quota, and an error after posting may duplicate a review; the workflow comment makes this bounded trade-off explicit. A clean replay after late side effects is not claimed.

Validation: actionlint and diff whitespace checks pass. The previously quota-failed review was rerun. All 3 checks pass on final head 16d0afc7102f8f69f9de1386144814048681c214; the latest Pullfrog review reports no new issues. The fallback secret is present (name/presence verified only, no secret value read). This infrastructure PR stays separate from the application changes and takes effect when merged into the default branch.

CodeRabbit automatic review is skipped for this Android repository due to its stars threshold.

PULLFROG_OAUTH_FALLBACK can hold a second subscription token, but the workflow
only ever passed the standard names — all unset here — so the agent's single
credential was the one Pullfrog holds at account scope. That limit is per
account and shared with interactive local use of the same subscription, and an
afternoon of local work exhausts it: in the Pinakes repository every review from
16:08 onward failed with `429 ... would exceed your account's rate limit`
followed by `no other model or provider was selected`, after two that had
succeeded earlier.

A per-account limit is not fixed by a second expression — the first credential
has to be tried and seen to fail — so this is a second attempt, not an `||`.
Written as `secrets.A || secrets.B` it would be worse than nothing: with no
repository-level token set, that resolves to the spare one on every run and
drains both quotas at once.

Three details that are not obvious from the diff:

- Only the presence flag is hoisted to job level, not the token. A step-level
  `if:` cannot read the `secrets` context at all — GitHub refuses to parse the
  whole workflow with `Unrecognized named-value: 'secrets'`, which I confirmed by
  dispatching a throwaway workflow rather than assuming. `env` is readable there,
  hence the flag.
- The provider keys stay on the agent steps. Moving them to job level would put
  every token in the environment of the checkout and of the final `run:` too,
  which cuts against `persist-credentials: false` and `shell: restricted`.
- The fallback step passes only the fallback token. If another provider were
  configured and usable, the first step would already have reached it, since
  Pullfrog walks its credentials in order.

Degrades safely: with the secret unset, HAS_OAUTH_FALLBACK is false, the second
step is skipped and behaviour is exactly as before. The final step restores the
failure the first step's continue-on-error held back, so a review that genuinely
cannot run still reports red instead of passing silently.

This repository still needs the secret itself to be set for the fallback to do
anything.
@coderabbitai

coderabbitai Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 9796427b-b22e-4e0b-9039-7398ea3b88a7
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@fabiodalez-dev

Copy link
Copy Markdown
Owner Author

Aggiornamento: il secret è ora impostato su questo repository e il fallback è stato verificato in esecuzione, non solo nella forma.

Run su pinakes-docker: https://github.com/fabiodalez-dev/pinakes-docker/actions/runs/37692603870

step esito
Run agent fallito, 429 ... would exceed your account's rate limit
Run agent with the fallback subscription token riuscito
Fail when no attempt succeeded saltato
job verde

Nel log si vede il passaggio: il primo step chiude con no other model or provider was selected, il secondo riparte e completa senza errori. HAS_OAUTH_FALLBACK: true.

Questo risolve anche il dubbio che avevo lasciato aperto: un run salvato dalla riserva riporta verde, non rosso. Il timore era che il primo step marcasse il check dell'action come fallito prima che continue-on-error ne assorbisse l'uscita, lasciando un check rosso accanto a una review andata a buon fine. Non succede.

Resta vera la distinzione conclusion / outcome: nel run precedente, senza il secret, lo step fallito appariva success nella lista degli step perché continue-on-error ne riscrive la conclusion, mentre outcome conservava failure. L'if: legge outcome, quindi la condizione è scritta sul campo giusto — ma chi legge solo la lista degli step conclude il contrario.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ℹ️ No defects found in the workflow logic. Two open questions about how the Pullfrog action behaves at runtime are below. I could not settle them from this repository, so I am not approving.

Reviewed changes

This review covers the single commit that adds a second-attempt fallback to .github/workflows/pullfrog.yml.

  • Presence flag at job level: HAS_OAUTH_FALLBACK is set from secrets.PULLFROG_OAUTH_FALLBACK != ''. Only the boolean is hoisted, so a step if: can read it without exposing the token.
  • Primary attempt made non-fatal: Run agent gets id: agent and continue-on-error: true.
  • Fallback step: a second run of the same pinned action receives only CLAUDE_CODE_OAUTH_TOKEN from the fallback secret. It runs only when the first attempt failed and the flag is 'true'.
  • Failure restored: a final run: step exits 1 when the primary attempt failed and the fallback did not succeed.

I traced the conditions and they behave as the PR description says:

  • continue-on-error keeps the job status at success, so the implicit success() on the later steps holds, while steps.agent.outcome stays failure.
  • A skipped fallback has outcome == 'skipped', so the final step fires.
  • A fallback that fails is not continue-on-error. The job is then already red, so skipping the final step loses nothing.
  • The token never reaches the checkout step or the final run: step.

ℹ️ Unclear whether the fallback token takes priority over Pullfrog's account-level credential

The fallback step still runs with id-token: write, so Pullfrog can mint its account-level credential there too. Nothing in this repository shows whether an env-provided CLAUDE_CODE_OAUTH_TOKEN is used before that credential. If the account-level credential still comes first, the fallback hits the same 429 and adds nothing. Your dispatch test could not cover this because the secret was absent.

Technical details
# Confirm that the fallback step actually uses PULLFROG_OAUTH_FALLBACK

## Affected sites
- .github/workflows/pullfrog.yml:150-159 — the fallback step sets only `CLAUDE_CODE_OAUTH_TOKEN`, while job-level `id-token: write` (line 45) still lets Pullfrog mint its account-level credential
- .github/workflows/pullfrog.yml:146-149 — the comment says "Pullfrog walks its credentials in order", but the order between an env OAuth token and the OIDC-minted account credential is not established

## Required outcome
- Evidence that, with `CLAUDE_CODE_OAUTH_TOKEN` set in env, the agent authenticates with that token and not the account-level one. Possible sources: a run log after the secret is added, the `pullfrog` npm package source, or docs.pullfrog.com.

## Open questions for the human
- After adding the secret, does the fallback run's log show the env OAuth token being used?

ℹ️ The fallback runs after any failure of the first attempt, not only a rate limit

The fallback condition checks steps.agent.outcome == 'failure', which is true for any failure. A deterministic failure (a bad prompt, a tool or MCP error, an action bug) therefore re-runs the whole review and spends the second subscription's quota on a run that will fail the same way. It is also unverified whether a first attempt that fails late can leave side effects that the second run duplicates or contradicts. Examples would be a submitted review, a progress comment marked as errored, or a check run already concluded as failure.

Technical details
# Retry scope and first-attempt side effects

## Affected sites
- .github/workflows/pullfrog.yml:152 — `if: steps.agent.outcome == 'failure' && ...` does not distinguish a 429 from other failures

## Required outcome
- Either accept the trade-off and say so in the step comment, or narrow the retry to rate-limit failures if the action exposes an output or log signal that makes this possible.
- Confirm that a second run in the same job reuses the dispatch's progress comment and check run cleanly after the first run has failed, with no duplicate review and no stale red check left behind.

## Open questions for the human
- Is retrying on non-rate-limit failures acceptable given the shared quota on the fallback subscription?

Pullfrog  | Fix it ➔ | View workflow run | Using claude-opus-5-5 | 𝕏

@pullfrog

pullfrog Bot commented Oct 8, 2026

Copy link
Copy Markdown

Correction to my review. The first open question, whether the fallback token takes priority over Pullfrog's account-level credential, is now answered from the Pullfrog source at the pinned SHA. The concern does not hold — please disregard it.

  • selectConfiguredCredential in utils/credentialPool.ts tries the workflow-env credentials first. It only moves on to the repository and account candidates when no workflow credential is usable.
  • docs.pullfrog.com/keys states the same order: "the workflow's, then the repository's, then the account's".
  • The fallback step's CLAUDE_CODE_OAUTH_TOKEN is therefore tried first, even with id-token: write present.
  • A 429 makes the pre-flight probe mark the token unusable and moves on to the next credential. This is in preflightClaudeSubscription in utils/claudeSubscription.ts. The comment at lines 146-149 is accurate for this case.

The same source reading added two caveats. Neither blocks the PR:

  • The fall-through happens only in the pre-flight check, before the agent starts. The 429 text you quoted is the pre-flight's final throw, so this step covers it. A rate limit hit while the agent is already running would not switch credentials.
  • Pullfrog's docs advise against putting Claude subscription credentials in Actions secrets (model-access) because those tokens rotate. A long-lived claude setup-token value may be exempt, but I have not verified that. Connecting a second subscription in the Pullfrog console would give the same fallback with no second workflow step. It is worth weighing before you add PULLFROG_OAUTH_FALLBACK.

The second open question — the fallback also runs on failures unrelated to a rate limit — still stands. The source confirms it: 5xx and network errors in the pre-flight check count as usable, so a run that fails for those reasons still triggers the fallback step.

Pullfrog  | View workflow run | via Pullfrog | Using claude-opus-5-5 | 𝕏

@pullfrog

pullfrog Bot commented Oct 8, 2026

Copy link
Copy Markdown

Follow-up on the second open question: what a failed first attempt leaves behind. I read the Pullfrog source at the pinned SHA. The short answer: retrying after a pre-flight failure is safe; retrying after the first attempt has already posted its review is not.

  • Pre-flight 429, the case this PR targets: retrying is safe. The failure happens before any GitHub write.
    • The second run reuses the same progress comment id and overwrites the first run's error text.
    • Its check-run finalize is an unconditional checks.update, so it re-patches the check from failure to success.
    • The only side effect is cosmetic: the check is red and the comment shows the error while the second attempt runs.
  • The action gives no signal that separates a 429 from other failures. action.yml declares a single result output, set only on success, and failures go through a plain core.setFailed. steps.agent.outcome is the only usable signal, so the any-failure retry scope cannot be narrowed from the workflow.
  • The risky case is a first attempt that fails after submitting its review, for example a later timeout, or a rate limit hit during follow-up work.
    • The duplicate-review guard in mcp/review.ts only tracks state inside the running process, so the second run would review the PR again.
    • Submitting a review also deletes the progress comment. The second run is seeded with that deleted id, and a final progress update on an issue-type comment rethrows on 404. Inferred, not observed: the retry could then fail too.

My suggestion: keep the retry, but note this narrow failure mode in the step comment. A duplicate review is a minor cost next to a run that never happens, and the action offers nothing that would let the workflow skip the retry once a review is already posted. None of this blocks merging.

Pullfrog  | View workflow run | via Pullfrog | Using claude-opus-5-5 | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ No new issues found. Both open questions from the previous review are now settled.

Reviewed changes

This run covers the two commits pushed after the review of 7766cd0. Both commits add time limits and document the retry trade-offs.

  • Bounded attempts: added timeout: 20m to both agent steps and timeout-minutes: 45 to the job. timeout is a declared input in action.yml at the pinned SHA 0d318bef, with a default of 1h. If the job limit is reached during the fallback, the job is cancelled and shows red, so this cannot become a green no-op.
  • Retry scope documented: the fallback step comment now says the retry fires on any failure. It also says a deterministic error can spend fallback quota and a late failure can repeat a review. This addresses the second question as an accepted, explicit trade-off.
  • Credential order cited: added the claim that the workflow OAuth token is tried before account OAuth tokens, with a link to the source. I checked it against utils/credentialPool.ts at f0684f2. The options array lists workflow credentials before account candidates, and the stable subscription sort keeps that order. This addresses the first question.

One note, not a blocker: 20m replaces the 1h default for each attempt. A review that legitimately runs longer than 20 minutes will time out, retry, and probably fail red. The PR description says this is intended.

Pullfrog  | View workflow run | Using claude-opus-5-5 | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ No new issues found. The fallback step now has the same provider setup as the primary step, except for the OAuth token.

Reviewed changes

This run covers one commit, 16d0afc, pushed after the review of 8da49fe.

  • Restored provider keys on the fallback step: added the same 12 provider keys the primary step has (ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY, GEMINI_API_KEY, XAI_API_KEY, DEEPSEEK_API_KEY, MOONSHOT_API_KEY, KIMI_API_KEY, META_MODEL_API_KEY, OPENROUTER_API_KEY, AI_GATEWAY_API_KEY, OPENCODE_API_KEY) in the same order. Only CLAUDE_CODE_OAUTH_TOKEN differs, and it now comes from PULLFROG_OAUTH_FALLBACK. I compared the two blocks line by line and the lists are identical. A run configured for a non-Claude model therefore works the same way on the retry.
  • Rewrote the step comment: removed the old reasoning ("the step above would already have reached it"). The new comment says only the OAuth token is replaced. The keys are still set only on the two agent steps. Checkout and the final run: step get none of them, which matches the job-level comment at lines 38–39.

The 13-key block now appears in both agent steps. If one block changes, the other must be changed too. A YAML anchor cannot remove the duplication, because GitHub Actions does not support merge keys (<<:) and the token has to be overridden. Moving the keys to job level would put them in the checkout step's environment, which this PR avoids on purpose. The duplication is the right trade-off.

Pullfrog  | View workflow run | Using claude-opus-5-5 | 𝕏

This branch has not been deployed

No deployments
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.

1 participant