Skip to content

fix(usage): share one Codex limits read and let complete reads drop b…

MacroscopeApp / Macroscope - Effect Service Conventions completed Sep 3, 2026 in 12m 47s

Effect Service Conventions: 1 issue found

  • apps/server/src/usage/usageLimits.ts β€” the UsageLimitsStore constructor reads process.env and process.cwd() directly instead of acquiring them from the Effect environment (HostProcessEnvironment, ServerConfig.cwd), as the sibling UsageService.make does.

Other changed service code follows the conventions: subpath namespace imports for Effect modules, inline interface on the Context.Service tag with make and layer exported under their canonical names, dependencies acquired via yield* in make, and Layer.effect/Layer.succeed used appropriately (including the inert layerTest).

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.

Files examined (in scope: apps/**/*.ts, packages/**/*.ts, excluding **/*.test.ts):

  • apps/server/src/usage/usageLimits.ts (new service module)
  • apps/server/src/usage/UsageService.ts
  • apps/server/src/provider/Layers/CodexProvider.ts
  • apps/server/src/server.ts
  • packages/contracts/src/usage.ts
  • packages/shared/src/usageFormat.ts, packages/shared/src/usageMerge.ts

Notes on items checked and found acceptable:

  • No named imports from the bare "effect" package; all Effect modules imported as subpath namespaces.
  • usageLimits.ts exports plain make/layer (the earlier makeStore naming finding was addressed in 038e3836) and defines the store interface inline on Context.Service, referring to it as UsageLimitsStore["Service"].
  • layer uses Layer.effect over a real make effect and forks the provider event subscription with forkParked, matching existing server layer patterns; layerTest is an inert Layer.succeed.
  • No new error classes, predicates, or diagnostic-suppression directives in non-test files; the @effect-diagnostics nodeBuiltinImport:off directive is in the excluded test file and carries an adjacent explanation.
  • No ManagedRuntime/runPromise usage introduced in domain services.

πŸ” Agent activity β€” 8 tool calls

1. browse_code β€” apps/server/src/usage/usageLimits.ts

2. browse_code β€” apps/server/src/usage/UsageService.ts

πŸ’­ I'm checking whether usageLimits.ts follows the repo's convention of importing local modules directly versus treating a service boundary as a namespace import, looking at how it pulls in config, ProviderService, readCodexRateLimits, and forkParked. Then I'm reviewing the make/layer naming, which looks correct, and examining the ordering of declarations in the file β€” imports, schemas, several pure functions, then the Context.Service tag, followed by layerTest, more schemas, make, and layer β€” noting that this deviates slightly from the canonical order of imports, schemas, tag, make, layer, and weighing whether that's worth flagging. Actually, checking UsageService confirms the same pattern (tag, then layerTest, then make), so this ordering is consistent with existing repo style and doesn't need flagging. Looking at the refresh logic's use of a mutable closure with an unsafe deferred, I conclude this is local state rather than a hidden dependency, so it's not a real convention violation, though it may be a correctness concern separate from style. I also note the broad catchCause error handling and the pattern of acquiring ChildProcessSpawner and re-providing it as a service, neither of which appear to violate any listed conventions. For suppression comments, I check whether any non-test files disable lint or type checks without justification β€” the only cast I find is an "as unknown as" type assertion in the schema, which isn't a diagnostic-disabling directive, so no violation there either. Turning to server.ts, I trace how UsageLayerLive gets wired: it composes UsageService.layer with the settings layer and UsageLimits.layer, but ProviderService still isn't provided at that point, matching the comment that usage sits ahead of the provider runtime that eventually satisfies it. That seems consistent with the intended design rather than a slip. Checking imports and contracts next β€” the usage.ts contracts file and server.ts both use proper namespace imports, and the withDecodingDefaultKey usage looks fine. I'm also verifying that usageFormat.ts and usageMerge.ts stay pure helper modules, but I notice usageMerge.test.ts imports a USAGE_MERGE_COMPATIBLE_SINCE constant from contracts that I don't see defined anywhere in the contracts diff, so I need to track down where that's actually exported from. Now looking at CodexProvider.ts, it exports a CodexAppServerLaunch interface plus helper functions for connecting to the app server and reading rate limits, using a request call to an "account/rateLimits/read" endpoint β€” that all looks fine. Since the earlier review pass only flagged one naming issue that's since been fixed, I'm scanning for anything new: there's an Effect.catchCause that silently swallows defects, which isn't explicitly covered by the convention list, and a test layer using Layer.succeed with inert effects, which is allowed since it doesn't call runtime-backed APIs. The more interesting candidate is that make reaches directly into process.env and process.cwd() instead of going through the Effect environment or the HostProcessEnvironment service that UsageService already uses in the same directory β€” that looks like a genuine violation of the "don't hide dependencies in module globals or imperative runtime APIs" convention, so I want to flag it as a real finding.

3. browse_code β€” What is HostProcessEnvironment in @t3tools/shared/hostProcess β€” its service interface and how do server services acquire process env/cwd? Is direct use of process.env / process.cwd() inside Effect service constructors in apps/server common, or is it required to go through HostProcessEnvironment? Show examples.

4. browse_code β€” Show how other apps/server modules call probeCodexSkillsForCwd (CodexProvider.ts) β€” what do they pass for cwd, environment, and launchArgs, and do they obtain the environment from a service or from process.env directly?

πŸ’­ I'm noticing that usageLimits.ts uses process.env and process.cwd() directly instead of the existing HostProcessEnvironment and ServerConfig.cwd abstractions the codebase already providesβ€”since config is already yielded in make, config.cwd would be the more consistent choice here. Unlike CodexDriver, which properly merges per-instance environment through a helper, this code bypasses the dependency-injection pattern the convention rule calls out, making it a more defensible finding rather than just a style nitpick. I want to check other Effect construction patterns before settling on this. Looking at Layer.effect(UsageLimitsStore, ...), the make is a proper Effect, so that aligns fine with the convention. But I should verify whether the layer forking a stream subscription needs Layer.scoped instead, since forking parked work likely requires a Scope.

5. browse_code β€” Show the implementation and signature of forkParked in apps/server/src/serverActivation.ts, and list other modules that call it inside a Layer.effect / Layer.scoped constructor. Does it require a Scope?

6. browse_code β€” apps/server/src/usage/UsageService.ts

πŸ’­ The Deferred pattern and single-flight approach in UsageService line up with the new usageLimits refresh logic, and the layer setup using forkParked also matches existing repo conventions. I'm now checking whether usageLimits.make should be using HostProcessEnvironment or ServerConfig.cwd instead of raw process.env and process.cwd() calls, to stay consistent with the codebase's conventions. Since UsageService in the same directory yields HostProcessEnvironment explicitly, while the new module bypasses it with a direct ambient global read, this looks like a defensible convention violation worth flagging β€” reading process.env directly conflicts with the guidance against hiding dependencies behind imperative APIs. I want to verify exactly how UsageService uses hostEnvironment to make this suggestion concrete before finalizing it, while also confirming the other pieces (scoped requests, provided services, pure helper functions) are all fine.

7. browse_code β€” In apps/server/src/usage/UsageService.ts, where is the hostEnvironment (HostProcessEnvironment) value used? Show the lines. Also does ServerConfig have a cwd field and is it used anywhere in apps/server/src/usage/?

πŸ’­ I'm noticing that usageLimits.ts's make constructor reaches into process.env and process.cwd() directly instead of pulling them from the environment like UsageService.make does in the same directory, breaking that convention and making the store harder to test or override. Checking the rest of the changed .ts files, though β€” usageMerge, contracts/usage.ts, and the imports around CodexProvider all look fine, using proper named imports and Effect patterns. I don't see any other suppressed lint rules or service-layer violations outside that one issue in usageLimits.ts. I'm settling on the single ordering nitpick being too minor to flag, since layerTest placement isn't strictly rigid in the codebase style anyway. So I'll go with just the one finding and now I'm drafting the inline comment with a suggested fix for the readCodexRateLimits call. The proper fix would require acquiring HostProcessEnvironment in make and using config.cwd, but that spans multiple hunks (import, acquisition, usage), so a single-hunk suggestion diff won't work here β€” I'll skip the diff and just point to the target lines in the comment instead.

8. modify_pr β€” Submitted review with 1 inline comment


Agent Credits: 704 credits

Token usage: 104 uncached input Β· 3,111,524 cache-read input Β· 619,140 cache-write input Β· 50,861 output
Agent Credits may also include non-token charges from external tools such as web research.