Skip to content

fix(run): refresh OAuth session in local partner-credential injection (BE-3361)#552

Open
mattmillerai wants to merge 2 commits into
mainfrom
matt/be-3361-partner-cred-refresh
Open

fix(run): refresh OAuth session in local partner-credential injection (BE-3361)#552
mattmillerai wants to merge 2 commits into
mainfrom
matt/be-3361-partner-cred-refresh

Conversation

@mattmillerai

Copy link
Copy Markdown
Collaborator

ELI-5

When you run a workflow locally that uses a partner-API node (Veo/Kling/BFL/Gemini/…), the CLI has to hand the local server a credential so the node can authenticate. If you're signed in with OAuth (the normal way), it grabs your login token.

The catch: OAuth access tokens expire fast (that's by design). The local injector was reading your session without ever refreshing it, so once your short-lived token lapsed — which happens routinely between commands — it saw an "expired" session, skipped it, and you got a partner_node_requires_credential error even though you were signed in. That was the "intermittent while signed in" failure from the 2026-07-16 dogfooding session.

This makes the injector refresh the token when it can (spending the long-lived refresh token, exactly like the cloud path already does), so a signed-in user keeps working. And it does it in a way that can never log you out: if the refresh hits a fatal error, the shared session is left intact and the code just falls through to the env var / stored key like before.

What changed

  • comfy_cli/command/run/credentials.py_resolve_partner_credential() now calls resolve_cloud_credential(purpose="cloud", refresh=True, allow_clear=False) instead of refresh=False. Module + function docstrings updated to document the new contract (refresh when possible, never clear, fall through on failure).
  • comfy_cli/credentials.pyresolve_cloud_credential() gains an allow_clear: bool = True keyword, forwarded into get_session(refresh=refresh, allow_clear=allow_clear)ensure_fresh_session. get_session already accepted/forwarded allow_clear, so no change was needed there.

Why it's safe

  • Never destroys the shared login. allow_clear=False means a fatal refresh error (invalid_grant / reuse-detection) returns None without clearing the stored session (oauth.py already implements this; verified by an existing oauth-layer test + a new end-to-end test here). The foreground command still owns the session lifecycle.
  • Network flake behavior is unchanged. ensure_fresh_session returns the stale session on a transient refresh failure; it fails its own expiry check and the resolver falls through to env/stored key — identical to the pre-change behavior.
  • Concurrent comfy run fan-outs are safe — the cross-process refresh lock (oauth.py) coalesces concurrent refreshes so a rotated refresh token is never replayed.
  • Other callers unaffected. allow_clear defaults to True, and get_session already defaulted allow_clear=True, so the two existing callers (target.py, generate/client.py) keep identical behavior.
  • This mirrors the already-shipped proactive-refresh on the cloud path (where.py), with the added allow_clear=False nuance appropriate for a best-effort injector.

Tests

  • tests/comfy_cli/test_credentials.py — new TestResolveAllowClear (end-to-end against a real tmp secret store): expired session + valid refresh token → new access token returned; fatal OAuthRefreshError with allow_clear=False → session not cleared, falls through to env key; transient refresh failure → stale session kept, falls through; plus boundary checks that allow_clear is forwarded and defaults to True.
  • tests/comfy_cli/command/test_run.pyTestResolvePartnerCredential updated to the refresh contract: refreshes-and-uses the OAuth token, passes allow_clear=False, transient-stale falls through, env/stored precedence.
  • The no-direct-reads ratchet (test_no_direct_credential_reads_outside_resolver) still passes — no new direct os.environ/ensure_fresh_session reads were added outside credentials.py.

Full targeted suite: tests/comfy_cli/test_credentials.py + tests/comfy_cli/command/test_run.py + tests/comfy_cli/auth/ → 130+ passed; ruff check / ruff format --check clean.

Not in scope

Per the ticket: the partner_node_requires_credential error message/hint text (separate ticket) and any MCP-side changes (BE-3344).

… (BE-3361)

The local partner-node credential injector called resolve_cloud_credential
with refresh=False, so a signed-in OAuth-only user whose short-lived access
token had lapsed was skipped here and deterministically hit
partner_node_requires_credential on any local run — the 'intermittent while
signed in' failure from dogfooding.

Resolve with refresh=True so the lapsed token is refreshed, and add an
allow_clear kwarg to resolve_cloud_credential (forwarded through get_session
into ensure_fresh_session) so this best-effort path passes allow_clear=False:
a fatal refresh error never clears the shared session, and a transient failure
returns the stale session that fails its own expiry check and falls through to
env/stored key exactly as before.
@mattmillerai mattmillerai added the agent-coded PR authored by the agent-work loop label Jul 17, 2026
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 7 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9a2eb4c6-7822-41ff-b6f3-d542adc1091f

📥 Commits

Reviewing files that changed from the base of the PR and between a732f5c and 64748c6.

📒 Files selected for processing (5)
  • comfy_cli/command/run/__init__.py
  • comfy_cli/command/run/credentials.py
  • comfy_cli/credentials.py
  • tests/comfy_cli/command/test_run.py
  • tests/comfy_cli/test_credentials.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch matt/be-3361-partner-cred-refresh
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch matt/be-3361-partner-cred-refresh

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

@mattmillerai
mattmillerai marked this pull request as ready for review July 17, 2026 21:06
@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. bug Something isn't working labels Jul 17, 2026
@mattmillerai mattmillerai added the cursor-review Request Cursor bot review label Jul 17, 2026

@github-actions github-actions 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.

🔍 Cursor Review — Consolidated panel

Triggered by @mattmillerai.

Found 3 finding(s).

Severity Count
🟡 Medium 1
🟢 Low 1
⚪ Nit 1

Panel: 6/8 reviewers contributed findings.

Reviewers that did not contribute: kimi-k2.5:adversarial (empty), kimi-k2.5:edge-case (empty)

Comment thread comfy_cli/command/run/credentials.py Outdated
Comment thread comfy_cli/credentials.py
Comment thread comfy_cli/command/run/credentials.py Outdated
…BE-3361)

Address the cursor-review panel findings on the refresh=False→True switch:

- Skip _resolve_partner_credential() when an explicit --api-key already
  populated extra_data: the resolver can perform a synchronous OAuth refresh
  (network POST + refresh-lock wait), and an explicit key already satisfies
  the partner node — so an explicit-key run stays network-free.
- Make the injector truly best-effort: ensure_fresh_session only swallows
  transient/timeout cases, so an unexpected OSError (acquiring the refresh
  lock or persisting the rotated token) previously propagated and aborted the
  run. Catch it and fall through to a network-free env/stored-key read, exactly
  the pre-BE-3361 behavior on this path.
- Correct the docstring's leeway over-claim: a token inside the 30-60s window
  is returned as live rather than falling through.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mattmillerai

Copy link
Copy Markdown
Collaborator Author

CI note: the red `build` and Windows `test` checks are the two known repo-wide infra breaks, not this PR:

  • `build` — 9 failures in `registry/test_config_parser.py` + `nodes/test_node_init.py` (`ValueError: Comment cannot contain line breaks`) from a newer tomlkit rejecting multi-line comments; tracked as BE-3290. This PR touches none of those files.
  • Windows `test` — `ImportError: cannot import name 'version' from 'pydantic_core'` in the dependency install step; an infra flake failing on every open PR.

All partner-cred / run tests pass locally (131 passed), ruff + format clean. Cursor-review threads addressed in 64748c6 and resolved.

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

Labels

agent-coded PR authored by the agent-work loop bug Something isn't working cursor-review Request Cursor bot review size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant