fix(console): LocalizationFetchProvider 重试瞬时失败 —— 一次冷环境 503 不该让区域设置整个会话失效 - #3098
Merged
os-zhuang merged 1 commit intoJul 31, 2026
Merged
Conversation
…ization failure `/auth/me/localization` is served by the environment kernel that owns the session on a multi-tenant host, and a cold one answers 503 + `Retry-After` while it warms (objectstack#4159). A transient failure is a normal part of a cold start there, not an exception. The provider made ONE attempt and `.catch()`-ed into silence, so a single 503 during warm-up left currency and locale unset for the WHOLE session — every money field a plain number, nothing ever trying again, long after the kernel was ready. Silent and permanent, which is why nobody would report it as a bug. I had looked at this file during objectui#3052 and judged it "cosmetic, degrades gracefully, no change needed". That was right at the time and wrong afterwards: #4159 turned 503 from an anomaly into this endpoint's routine answer, and the judgement was never revisited. The asymmetry with MePermissionsProvider — same host, same provenance, same 503, opposite handling — is what surfaced it. PRIMITIVES SHARED, POLICY NOT. "Is this transient", "how long to wait" and the `Retry-After` parsing move from @object-ui/permissions' internal module to @object-ui/types — the lowest package both callers reach — and `PermissionsFetchError` becomes the generic `HttpFetchError`. One definition of transient rather than a second copy free to drift. No behaviour change for MePermissionsProvider. The policies stay separate because the postures are opposite: permissions is FAIL-CLOSED and holds its loading state across the waits (rendering a permissive default early is a real hazard); localization is COSMETIC and keeps rendering children throughout, filling the value in if and when an attempt succeeds. Its budget is also more patient (4 × 1s base vs 3 × 500ms) — nothing waits on it, so it can still be trying when a slow environment finishes warming. Tests: 8 new, where there were none. One pins the POSTURE rather than the behaviour — children must be rendered while the fetch is still failing — because the plausible future regression here is someone "aligning the two providers" and carrying the fail-closed blocking onto a surface that must never block. Verified: console localization 8/8, permissions 124/124 (regression), full objectui suite 8998 passed / 24 skipped, type-check 78/78, changed-file lint 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016gEeLZ4oTeSXG6fKG1r3vd
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
os-zhuang
marked this pull request as ready for review
July 31, 2026 06:10
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
objectstack#4159 之后,
/auth/me/localization由拥有该会话的环境 kernel 应答,冷 kernel 会返回503+Retry-After。也就是说:在多租户宿主上,瞬时失败是冷启动的常规组成部分,不是异常。而这个 provider 只尝试一次就
.catch()静默。一次 503 会让货币与区域设置整个会话失效 —— 每个金额字段渲染成裸数字,且永远不会再试,哪怕 kernel 早已就绪。静默且永久,所以没人会把它当 bug 报上来。一处我要说明的判断变更
objectui#3052 时我看过这个文件,判定为「cosmetic、可优雅降级、无需改动」。那个判断在当时是对的,之后就错了 —— #4159 把 503 从异常变成了这条端点的常规答案,而那个判断没有被重新检视。
真正让它浮出来的是不对称:同一个宿主、同一个 provenance、同一个 503,两个 provider 处理方式相反。
修法:原语共享,策略不共享
重试原语(什么算瞬时、等多久、
Retry-After解析)从@object-ui/permissions的内部模块移到@object-ui/types(两个调用方都能到达的最底层包),PermissionsFetchError→ 通用的HttpFetchError。git 识别为重命名,共享意图在 diff 里就是显式的。MePermissionsProvider行为零变化。策略保持分开,因为姿态相反:
MePermissionsProviderLocalizationFetchProviderlocalization 更有耐心是刻意的:没有东西在等它,所以它可以在慢环境暖机结束时仍在尝试;对用户而言迟到的货币符号好过永远没有。
不重试的仍不重试:
401/403/404/500是关于调用者的真实答案,第一次就失败。测试:8 例,此前零覆盖
其中一例钉的是姿态而不是行为:
它防的是一个具体的未来回归 —— 有人"顺手对齐两个 provider",把 fail-closed 的阻塞语义搬到这个绝不能阻塞的面上。原语共享之后,这种对齐冲动只会更强,所以这条断言值得单独存在。
验证
type-check关联:objectstack#4159(503 的来源)、objectui#3052(permissions 侧的同类修复)、objectui#3050、cloud#927。
🤖 Generated with Claude Code
https://claude.ai/code/session_016gEeLZ4oTeSXG6fKG1r3vd
Generated by Claude Code