fix(server): open clients no longer stay on "Updating" after a provider update#27992

merged2
main
fix/issue-24998
opened2d agobyJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 3h ago

Problem

After a provider update, open clients can keep showing "Updating Claude" with a spinner even though the server has recorded the final result. The sidebar pill and the provider row stay on "Verifying the installed version" until the page is reloaded.

To reproduce on main: run the web dev server with a native claude whose update prints progress for 20 s and exits 0 without changing the version. Then open Settings → Providers → Claude → Update now. The server.updateProvider RPC succeeds and the server writes the terminal unchanged state, but the open window never leaves "Updating Claude". A reload shows the correct state.

Change

upsertProviders in ProviderRegistry commits the new provider list to providersRef and writes the provider status cache. Only after that write does it publish the list it captured before the write. If a newer upsert lands during that write, the older list is published last. The newer upsert here is the terminal update state, which does not persist. Live subscribers then end on the stale running value, while providersRef, which new connections read, is correct.

The fix publishes the current providersRef after the cache write instead of the captured list. The last value on streamChanges now always matches the registry. A terminal state write still never waits on a disk write. The other publish sites in the file publish directly after their own Ref.modify and do not have this gap.

Scope and approval

Fixes pingdotgg/t3code#24998

Triaged via the automated issue flow.

Verification

  • New test ProviderRegistry > streams the terminal update state when it lands during a slow cache write in apps/server/src/provider/ProviderRegistry.test.ts. It holds the provider status cache write on a Deferred, sets the terminal update state during that write, then releases the write.
    • On main it fails: expected { status: 'running', message: 'Verifying the installed version', … } to deeply equal { status: 'unchanged', … }.
    • With the fix it passes, 5 runs in a row.
  • ProviderRegistry.test.ts 49/49, providerMaintenanceRunner.test.ts 28/28, ws.test.ts 9/9. vp run check:changed passes (format, lint, knip, typecheck, tests).
  • Runtime, web dev server on Linux, fake native claude as above:
    • On main, 2 of 2 runs ended stuck on "Updating Claude · Verifying the installed version". Temporary taps on the registry change stream in the second run showed the stale publish:
      105:38:11.367 registry "Update command completed, but T3 Code still detects an outdated provider version."
      205:38:11.377 registry "Verifying the installed version"
      305:38:11.578 emitted to client "Verifying the installed version"
    • With the fix, 2 of 2 runs ended on the terminal pill "Claude still needs an update", with no reload.
  • Not checked: macOS desktop with the "Update everything" flow the follow-up report used, mobile, and a real claude update that changes the version. The fix is in the server path that all of those clients share.

Server-only change, so there are no before/after captures.

Model and harness: Claude Opus 5.5 (claude-opus-5-5) via Claude Code in T3 Code; review by GPT-6.1 Sol (Codex) and Claude Opus 5.5.

🤖 Generated with Claude Code

Checks

24 successful · 17 skipped
All checks passed
6a3f070

Added 2 commits

  • test(server): reproduce stale provider update state after slow cache write3dbc809
  • fix(server): provider update state no longer sticks on "Updating" in open clients6a3f070
Synced from GitHub

Approvability

Verdict: Approved at 6a3f070

Macroscope's review found this PR approvable — Provider-status notifications now read the current registry after cache writes, preventing older update progress from replacing a completed status in open clients. The change leaves persistence and update execution unchanged, with targeted regression and runtime verification reported.

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

Synced from GitHub

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire5.0 KiB5.0 KiB−6 B (−0.1%)6.8 KiB✅
CodexThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiB✅
CodexLive turn WebSocket wire1.3 KiB1.2 KiB−6 B (−0.5%)2.0 KiB✅
CodexLive turn WebSocket decoded21.6 KiB21.6 KiB0 B (0.0%)24.4 KiB✅
CodexLive turn messages220 (0.0%)8✅
ClaudeTotal thread wire5.0 KiB5.0 KiB0 B (0.0%)6.8 KiB✅
ClaudeThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiB✅
ClaudeLive turn WebSocket wire1.3 KiB1.3 KiB0 B (0.0%)2.0 KiB✅
ClaudeLive turn WebSocket decoded21.9 KiB21.9 KiB0 B (0.0%)24.4 KiB✅
ClaudeLive turn messages220 (0.0%)8✅

Baseline: 6d46336 · PR result: 6a3f070 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Synced from GitHub

Review in Change Stack →

No actionable comments were generated in the recent review.

External image Recent review info
External image Run configuration
External image Commits

Reviewing files that changed from the base of the PR and between 6d46336476941257d774be7c7733e9caf201fec2 and 6a3f070529f3e0601b88c30f45036a5992c90594.

External image Files selected for processing: 2
  • apps/server/src/provider/ProviderRegistry.test.ts
  • apps/server/src/provider/ProviderRegistry.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.

Walkthrough

ProviderRegistry now publishes its current provider state after persistence. A concurrency test delays a cache rename and checks that a later provider update state remains present in the registry and in the emitted provider list.

Changes: Provider state publication

Layer / File(s)Summary
Publish current provider state
apps/server/src/provider/ProviderRegistry.ts, apps/server/src/provider/ProviderRegistry.test.ts
After persistence, upsertProviders publishes the current providersRef value. The regression test delays a cache rename and verifies that the terminal unchanged state is retained.

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested labels: bugfix, size:M

Suggested reviewers: t3dotgg, maria-rcks

Merge Risk: External image Minimal · up to 6a3f0

This change keeps clients from staying on "Updating" after a provider update by publishing the latest provider state rather than an older one. No actionable merge-blocking risk is evident from the supplied context.

Pre-merge checks: Passed 4
Check nameStatusExplanation
Title checkPassedThe title clearly and concisely describes the primary server fix: open clients now receive the final provider update state instead of remaining on "Updating".
Description checkPassedThe description includes all required sections: Problem, Change, Scope and approval, and Verification. It provides reproduction steps, implementation details, linked issue context, focused test result…
Linked Issues checkPassedThe PR addresses open issue #24998. ProviderRegistry.upsertProviders now publishes providersRef after the provider-status cache write, so a newer terminal update state cannot be replaced by an old…
Out of Scope Changes checkPassedThe PR changes only apps/server/src/provider/ProviderRegistry.ts and its focused test. The production change fixes the provider update publication race. The test verifies that behavior. No unrelated…
Full details: Description check

Explanation

The description includes all required sections: Problem, Change, Scope and approval, and Verification. It provides reproduction steps, implementation details, linked issue context, focused test results, runtime validation, and unverified scenarios.

Full details: Linked Issues check

Explanation

The PR addresses open issue #24998. ProviderRegistry.upsertProviders now publishes providersRef after the provider-status cache write, so a newer terminal update state cannot be replaced by an older running snapshot. The new ProviderRegistry test reproduces the slow-write race and checks that the last streamed snapshot contains status: "unchanged". The change directly supports clients that must leave the running update state without a reload.

Full details: Out of Scope Changes check

Explanation

The PR changes only apps/server/src/provider/ProviderRegistry.ts and its focused test. The production change fixes the provider update publication race. The test verifies that behavior. No unrelated product behavior or unrelated files are changed.

Finishing Touches
Generate docstrings
  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts