Skip to content

fix(server): fail fast when stored CLI credential has no refresh token - #12024

Closed
Adamulek123 wants to merge 2 commits into
pingdotgg:mainfrom
Adamulek123:perf/s2-cli-refresh-carryover
Closed

Adamulek123 wants to merge 2 commits into
pingdotgg:mainfrom
Adamulek123:perf/s2-cli-refresh-carryover

Conversation

@Adamulek123

@Adamulek123 Adamulek123 commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Summary

When a grant response omits refresh_token, the CLI stores an empty string and then burns a doomed network round trip (refresh_token='') on every later refresh before falling through to a fresh login.

What changed

  • refresh in apps/server/src/cloud/CliTokenManager.ts fails immediately with the new no-cause CloudCliMissingRefreshToken tag (a member of the exported CloudCliTokenManagerError union) on an empty stored refresh token, preserving the existing fall-through-to-login contract without the wasted request.

Validation

  • vp test run apps/server/src/cloud/CliTokenManager.test.ts — 9/9 pass, including a new test that seeds an expired token with an empty refresh token and asserts refresh failure with zero HTTP calls.
  • Package typecheck clean.

Summary by CodeRabbit

  • Bug Fixes
    • Prevented failed credential refresh attempts when no refresh token is available.
    • The application now fails fast without making an unnecessary network request.
    • When stored credentials cannot be refreshed, CLI sign-in now smoothly falls back to fresh device authorization.
    • Cloud connection flows now handle missing refresh credentials gracefully and report the issue consistently.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XS 0-9 changed lines (additions + deletions). labels Sep 16, 2026
@macroscopeapp

macroscopeapp Bot commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This targeted fix prevents an invalid stored OAuth credential from making an empty refresh request and adds a regression test for the no-network path. Because it changes authentication and credential-refresh behavior across server flows, human review is warranted.

Notes:

  • All code in this push has already been reviewed. Approvability was decided on eligibility alone.

You can add or adjust custom eligibility rules. Learn more.

Comment thread apps/server/src/cloud/CliTokenManager.ts Outdated
@macroscopeapp macroscopeapp Bot added bug Something is broken or behaving incorrectly. and removed bug Something is broken or behaving incorrectly. labels Sep 16, 2026
@macroscopeapp

This comment has been minimized.

@coderabbitai

coderabbitai Bot commented Sep 16, 2026 •

Copy link
Copy Markdown

Review Change StackReview Change Stack

Important

Review skipped

We couldn't safely recover the incremental review. No full review was started, and the last reviewed checkpoint was preserved. Retry later, or explicitly request a full review by commenting @coderabbitai full review.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: pingdotgg/t3code/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 3040e548-1c95-4c38-a4ca-6478a1d87d8e


📥 Commits

Reviewing files that changed from the base of the PR and between 3b27df88beb70394e86da752aa195f21d77adc80 and ec3eb3927b858845970e28c97012d649bf671a5d.


📒 Files selected for processing (4)
  • apps/server/src/cli/connect.ts
  • apps/server/src/cloud/CliTokenManager.test.ts
  • apps/server/src/cloud/CliTokenManager.ts
  • apps/server/src/cloud/http.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 4 remain after this review.



📝 Walkthrough

Walkthrough

The refresh flow now returns a dedicated error for stored credentials without a refresh token. CLI authorization falls through to device authorization, HTTP reconciliation maps the error, and tests verify that no network request occurs.

Changes

CLI credential refresh handling

Layer / File(s) Summary
Missing refresh-token error contract
apps/server/src/cloud/CliTokenManager.ts
Adds CloudCliMissingRefreshToken. The refresh flow returns it for an empty refresh token, and getExisting preserves it while wrapping other failures.
Caller error handling
apps/server/src/cli/connect.ts, apps/server/src/cloud/http.ts
CLI authorization falls through to device authorization for the new error. Cloud-link reconciliation maps it to the existing HTTP error path.
Regression test
apps/server/src/cloud/CliTokenManager.test.ts
Verifies the tagged error for an expired credential without a refresh token and confirms zero outbound HTTP calls.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Suggested reviewers: juliusmarminge


Merge Risk: ⚪ Minimal · up to ec3eb

Missing refresh tokens now fail without an unnecessary request, while CLI login fallback and other error handling remain covered.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 4…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the main change: failing fast when a stored CLI credential has no refresh token.
Description check ✅ Passed The description explains what changed, why it changed, and how it was validated. It does not use the template headings exactly and omits the checklist, but it provides the required information and the…


✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR



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

@github-actions github-actions Bot added size:M 30-99 changed lines (additions + deletions). and removed size:XS 0-9 changed lines (additions + deletions). labels Sep 18, 2026
Comment thread apps/server/src/cloud/CliTokenManager.ts
@macroscopeapp

This comment has been minimized.

@Adamulek123
Adamulek123 force-pushed the perf/s2-cli-refresh-carryover branch from ec3eb39 to c8e9f40 Compare September 22, 2026 16:04
@juliusmarminge juliusmarminge added the macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews label Oct 1, 2026 — with ChatGPT Codex Connector
@Adamulek123
Adamulek123 force-pushed the perf/s2-cli-refresh-carryover branch from c8e9f40 to 418ca49 Compare October 9, 2026 13:40
@Adamulek123

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (ec80933ac8cd) and cleaned up. mergeable is now MERGEABLE, down from CONFLICTING.

What the rebase surfaced. Two conflicts, both from work that landed after this branch was cut:

  1. cli/connect.ts — chore(lint): catch known tags with Effect.catchTags #16361 (ff397a2952, "chore(lint): catch known tags with Effect.catchTags") had already converted the catchTag call this PR was patching. Resolved by keeping this PR's shared fallThroughToDeviceAuthorization helper, which now covers both CloudCliCredentialRefreshError and the new CloudCliMissingRefreshToken. This handling is load-bearing, not cosmetic: without it the new tag escapes authorizeCli and hard-fails the command instead of falling through to a fresh sign-in.

  2. cloud/http.ts — hunk dropped. The target function is gone. reconcileDesiredCloudLinkWith moved to apps/server/src/cloud/CloudLink.ts:881, and failCloudCliTokenManagerError no longer exists anywhere in the repo (the whole reconcile path was extracted out of http.ts, which is now 150 lines). I checked whether the new tag needs a handler in its new home and it does not: CloudLink.ts:519, :531, and :562 all type their error as the full CliTokenManager.CloudCliTokenManagerError union, so widening that union with one more member propagates automatically. CloudLink.ts:887 and :1259 call getExisting with no tag handling, and nothing matched failCloudCliTokenManagerError. So this hunk was dead rather than relocated, and the diff is now 3 files / 100 insertions instead of 4 / 103.

Validation. apps/server/src/cloud/CliTokenManager.test.ts — 9 passed. The new test asserts the request count is exactly 0, so it verifies the fail-fast happens before the network round trip rather than just that the error type changed. I confirmed it is not a tautology by reverting the fix and re-running: without it the test fails with expected CloudCliCredentialRefreshError ... to be an instance of CloudCliMissingRefreshToken, which is also a direct demonstration of the old behavior making a doomed round trip. pnpm --filter t3 typecheck reports 0 errors (the remaining output is pre-existing suggestions in unrelated files), and vp lint on the three touched files is clean.

Note on the environment: this branch's node_modules was stale — Effect 4.0.0-rc.112 against a lockfile pinning 4.0.0-rc.115, which is why CI's last recorded run was 17 days old and had no Build/Typecheck/Test Web jobs. I refreshed the install with the filtered frozen-lockfile command, so the current run should exercise the full gate set for the first time in weeks.

The premise is unchanged and still live on main: CliTokenManager.ts:280 stores refresh_token: body.refresh_token ?? params.refresh_token ?? "", so an empty string is a legitimately reachable stored value, and the refresh at :457 posted it without checking. Behavior is otherwise identical — main already fell through to a fresh login after the failed round trip, so this removes a doomed request and gives the failure a specific type rather than wrapping it in a generic refresh error. No lockout risk: refresh only fails and never clears the stored token, hasCredential reads the store directly, and the recoverable deviceAuthorizationLogin path is untouched.

If CI comes back green I'd appreciate a re-look. If the CloudCliMissingRefreshToken type or the union widening is contentious, say so and I'll simplify to a narrower shape.

@Adamulek123
Adamulek123 force-pushed the perf/s2-cli-refresh-carryover branch from 418ca49 to a1e38f7 Compare October 9, 2026 13:46
@Adamulek123

Copy link
Copy Markdown
Contributor Author

Correction to my previous comment: I reported typecheck as clean. That was wrong, and CI caught it — the first run on the rebased head failed Typecheck with TS2375 in the new test.

What happened: after the rebase I "cleaned up" a effect(effectSucceedWithVoid) diagnostic by changing resolveFileManagerRevealKind: () => Effect.succeed(undefined) to Effect.void. The diagnostic was misleading here — that field is typed as a union including undefined under exactOptionalPropertyTypes, so void is genuinely not assignable. My local typecheck passed before I made that edit, and I did not re-run it after. Reverted to Effect.succeed(undefined); Typecheck now passes on a1e38f7ff3.

So my "validation" line in that comment overstated the evidence, and the honest sequence is: rebase → tests pass → typecheck pass → introduce the failure → CI typecheck fail → revert → typecheck pass. Worth noting because the diagnostic that tempted the change was a repo-wide suggestion, not an error, and I treated a suggestion as noise when it was flagging a real type constraint.

Everything else in that comment stands. For the record, verified on the current head:

  • apps/server/src/cloud/CliTokenManager.test.ts — 9 passed, including the new test asserting the HTTP call count is exactly 0.
  • Confirmed non-tautological by reverting the production fix and re-running: the test fails with expected CloudCliCredentialRefreshError ... to be an instance of CloudCliMissingRefreshToken, which also demonstrates the old doomed round trip.
  • Build, Lint, Test, Typecheck all pass. The full server/web test shards are still running.

The cloud/http.ts hunk remains dropped for the reason given: failCloudCliTokenManagerError no longer exists anywhere in the repo, and CloudLink.ts:519/:531/:562 already type their error as the full CloudCliTokenManagerError union, so the new tag propagates without a handler. Diff is 3 files / 100 insertions.

I'll hold off on further edits unless CI surfaces something else.

@maria-rcks

Copy link
Copy Markdown
Collaborator

Note

Written by claude-opus-5-5 on behalf of Maria

Hi! We are cleaning up open PRs, and this one does not say which model created it and was opened more than two weeks ago. If this change is still important, please rebuild it on current main with a newer model and note the model in the PR description.

@maria-rcks maria-rcks closed this Oct 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews size:M 30-99 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants