feat(usage): show Codex and Claude subscription limits on a Limits tab - #9507
Effect Service Conventions: no issues found
All clear
Details
Note
Your check run agent prompt is: .macroscope/check-run-agents/effect-service-conventions.md
More information about how Check Run Agents work can be found in our Help Center.
Reviewed the changed in-scope TypeScript (apps/**/*.ts, packages/**/*.ts, excluding tests) at head 20594ca: apps/server/src/usage/UsageLimitSources.ts, usage/cliproxyUsageLimits.ts, provider/Layers/claudeUsageLimits.ts, provider/Layers/codexUsageLimits.ts, provider/Layers/ProviderUsageLimitsIngestion.ts, provider/providerUsageLimits.ts, provider/makeManagedServerProvider.ts, provider/Layers/{ClaudeProvider,CodexProvider,ClaudeAdapter,CodexAdapter}.ts, serverSettings.ts, ws.ts, server.ts, plus the contracts, shared, and client-runtime modules.
Findings from earlier runs are addressed on this head:
codexRateLimitsFailureMessagenow takesCodexErrors.CodexAppServerErrorand narrows on_taginto bounded, client-safe messages, with the raw error logged ascause.- The hub read failure text is produced by
readFailureMessagefrom the statically known typed failure channel (HttpClientError/SchemaError/TimeoutError/InvalidUrl), and the structured failure is logged at debug instead of being copied onto the published snapshot. InvalidUrlis a tagged error carryingurland the underlyingcause.- The untargeted
serverRefreshProvidershandler now awaitsusageLimitSources.refreshrather than forking it into the per-request scope.
No new violations of the check's conventions on changed lines: Effect modules are imported as subpath namespaces (with effect/unstable/http named imports matching the repo-wide pattern), UsageLimitSources follows the single-module Context.Service + inline interface + make + layer shape and acquires HttpClient, ServerSettingsService, and BackgroundPolicy from the environment, no ManagedRuntime/runPromise was introduced into service construction, and no lint or type-checker suppression directives were added in the new modules.
🔍 Agent activity — 9 tool calls
💭 I'm checking the current head commit to see which flagged issues remain unresolved. The InvalidUrl thread was fixed using Data.TaggedError instead of my suggested Schema.TaggedErrorClass convention, but since the author already responded and resolved it, I'm weighing whether re-flagging that stylistic difference is worth being duplicative. Now I'm scanning UsageLimitSources.ts for remaining concerns — checking whether HttpClient imports from the http subpath follow the repo's namespace import convention, and looking at whether Semaphore is being used correctly with withPermits, plus verifying import ordering across the Ref and other effect module imports. I also notice codexPlanLabel is exported from CodexProvider and consumed cross-module by cliproxyUsageLimits, and that the diff dropped the
account.planType satisfies neverexhaustiveness check — a behavior change worth flagging even if it's not strictly an Effect convention violation. Let me pull up the actual files to confirm these before writing anything up.
1. browse_code — apps/server/src/usage/UsageLimitSources.ts
2. browse_code — apps/server/src/provider/Layers/codexUsageLimits.ts
💭 Looking at the failure handling pattern, using
Effect.resultwith a tag switch instead ofEffect.catchTagsseems like a reasonable alternative approach, and the convention doesn't explicitly flag this pattern as problematic. For InvalidUrl, the switch fromSchema.TaggedErrorClasstoData.TaggedErrorwas already addressed in a prior comment, and since it's an internal error that never escapes the service, this seems acceptable—no need to relitigate. Thecause: unknownfield also looks fine given it comes from anEffect.trycatch handler. On module ordering, I'm noting thatreadFailureMessageand theInvalidUrlerror class both appear after the tag declaration, with InvalidUrl defined after the function that references it—this was already part of my earlier feedback about keeping the imports → errors → tag → make ordering convention intact. I'm trying to figure out what's changed between my last review (c4059af) and the current head (20594ca): semaphore refresh work, the InvalidUrl error type switch, a patch-merge approach for usageLimitSources settings, an awaited refresh in ws.ts, and a new doc comment on resolveUsageLimitsAfterProbe. I want to focus specifically on the new code—the pure merge function in serverSettings.ts, the patch schema in contracts/settings.ts, and the secret handling logic in apps/server's serverSettings.ts—to see if anything there needs flagging. Checking apps/server's secret-reading error handling, it follows the existing ServerSettingsError convention, so that's fine. Moving to the ProviderUsageLimitsIngestion.ts file, its use of Effect.ignoreCause and Layer.effectDiscard looks consistent with patterns elsewhere. In claudeUsageLimits.ts, the exported Ref constructor and the function that records usage responses by taking a Ref directly rather than pulling it from a service environment seem acceptable, though I'm noting that ClaudeProvider's status-check function also takes a Ref as an explicit parameter instead of resolving it from context — worth keeping an eye on whether that's an intentional pattern or something to flag. I also notice the usage probe swallows a failure from a promise chain without logging it, which doesn't clearly violate any explicit convention but is worth double-checking against how the surrounding async function handles errors elsewhere. I want to look more closely at the ClaudeProvider file to see if this Promise-based probe follows established conventions for error handling within otherwise Effect-based code.
3. browse_code — apps/server/src/provider/Layers/ClaudeProvider.ts
4. browse_code — apps/server/src/provider/Layers/ClaudeProvider.ts
💭 I don't see obvious convention violations in the CodexProvider changes — the error handling with Effect.catch looks fine since it covers the whole error channel. I'm double-checking whether the typed error assumption could cause a type mismatch if the request fails differently, though the check passed, and I'm also noting the removal of the exhaustiveness check on planType. The function's signature now accepts a looser type, so it loses the compile-time guarantee that new plan types get handled — that's a real regression, but it's not something the "Effect Service Conventions" checklist covers directly, so I'm inclined to treat it as out of scope rather than flag it here.
5. browse_code — apps/server/src/provider/Layers/CodexProvider.ts
6. browse_code — apps/server/src/provider/Layers/CodexProvider.ts
💭 I'm checking that logging the failure cause as a debug annotation is fine even though the error message might embed a URL—since logs aren't wire payloads, preserving the exact cause value there is acceptable. Now I'm scoping which files are actually in play: the include patterns only cover
.tsfiles, so.tsxfiles across web and mobile are excluded, meaning several dialog components are out of scope. That leaves me focused on server, contracts, shared, client-runtime, and a couple of state/timestamp files, and I want to confirmServerProvider.tsfollows the convention of defining its interface inline inContext.Servicerather than a separate shape type. ThatServerProviderShapeinterface is pre-existing rather than newly introduced by the diff, so it's not something to flag. I want to look more closely atmakeManagedServerProvider.ts's new code, note thatproviderUsageLimits.tsis a new helper file without a module doc comment (though that's not an enforced convention), and check whether the semaphore usage and the import ofHttpClient/HttpClientResponseas named imports fromeffect/unstable/httpactually matches this repo's pattern of importing Effect modules as namespaces — I should grep to verify.
7. browse_code — How do other server files import HttpClient / HttpClientResponse from effect (e.g. import { HttpClient } from "effect/unstable/http" vs import * as HttpClient from ...)? Show a few examples of the exact import lines in apps/server/src and packages.
💭 That matches the repo convention, so I'll move on to checking the new code in makeManagedServerProvider.ts and ProviderUsageLimitsIngestion.ts more carefully, particularly how the ingestion logic forks a scoped effect inside Layer.effectDiscard and whether that pattern actually has access to a proper scope.
8. browse_code — apps/server/src/provider/makeManagedServerProvider.ts
💭 Now I'm checking the new error handling in serverSettings.ts, which uses ServerSettingsError with operation values like "read-secret", "write-secret", "remove-secret", and "remove-stale-secret" — that multi-value pattern is consistent with the existing convention, though I want to verify whether "remove-stale-secret" is actually part of the operation schema union. I'm also weighing whether previously flagged issues, like the placement of readFailureMessage relative to InvalidUrl, are worth mentioning again — they aren't, since that would just repeat prior feedback. I'm scanning claudeUsageLimits.ts and ClaudeAdapterLiveOptions for anything genuinely new, but the
makeClaudeScopedLimitNamesRef constant and the optionalscopedLimitNamesdependency both look like reasonable patterns rather than actual issues. Now I'm checking providerUsageLimits.ts and the contracts/shared modules for missing doc headers or convention violations, but nothing stands out there either. The more interesting question is in UsageLimitSources.ts, where they useEffect.resultwith a manual_tagswitch instead ofEffect.catchTags— this bypasses the recommended combinator and creates a fragility risk if a fifth error type is ever added to the union, since the switch has no default case to catch it. Actually since the switch is exhaustive over a declared TypeScript union with no default, the compiler would flag any unhandled case at the call site, so this pattern is type-safe despite not being the idiomatic combinator. I'll treat this as acceptable rather than a violation and move on. Now I'm looking at cliproxyUsageLimits.ts, which imports a named function directly from a large provider layer module rather than importing that module as a namespace — this seems to break the convention around importing local service modules as namespaces at service boundaries. That interface pre-exists, so adding a method to it isn't creating a new standalone shape — skip that flag. I'm also checking the UsageLimitSources service, which uses a getter returning a fresh stream each access, similar to existing layer patterns, and its refresh function swallows failures via ignoreCause while logging, keeping the typed Effect signature intact. Now I'm looking at whether a settings read failure could silently wipe published sources — ifgetSettingsfails and falls back to null via orElseSucceed without logging, that produces an empty entries object, which then publishes an empty snapshot list and clears all source rows. That feels like a real behavior bug rather than just a style nit, since a transient settings failure shouldn't erase already-published data, and it's worth flagging against the convention that failures should stay typed and visible rather than being silently swallowed into a fallback that has side effects. I'm leaning toward "all clear" since no new listed-convention violation stands out beyond what was already addressed, but I want to double-check the diff for any suppressed lint directives or suspicious type assertions added without justification—scanning for disable comments or unsafe casts in the non-test, in-scope code.
9. browse_code — In the files apps/server/src/usage/UsageLimitSources.ts, apps/server/src/usage/cliproxyUsageLimits.ts, apps/server/src/provider/Layers/claudeUsageLimits.ts, apps/server/src/provider/Layers/codexUsageLimits.ts, apps/server/src/provider/providerUsageLimits.ts, apps/server/src/provider/Layers/ProviderUsageLimitsIngestion.ts — are there any lint/type-checker suppression directives (oxlint-disable, eslint-disable, @ts-expect-error, @ts-ignore, biome-ignore)? Quote each occurrence with line numbers, or say none.
💭 Everything looks resolved with no new findings against the conventions, so I'll report "All clear" as the summary in my complete_check call, satisfying the minimum length requirement without needing any additional comments.
Agent Credits: 218 credits
Token usage: 28 uncached input · 819,088 cache-read input · 199,697 cache-write input · 16,463 output
Agent Credits may also include non-token charges from external tools such as web research.