Skip to content

Merge remote-tracking branch 'origin/main' into t3code/usage-page-sub…

MacroscopeApp / Macroscope - Effect Service Conventions failed Aug 27, 2026 in 5m 32s

Effect Service Conventions: 1 issue found

  • apps/server/src/usage/UsageService.ts — the new subscription-limits code inside UsageService.make reads the process environment from the process.env module global (lines 171, 217, 275, 284) instead of the HostProcessEnvironment reference the same constructor already acquires (hostEnvironment, line 154, used at line 387). Dependencies should come from the Effect environment rather than a module global, otherwise a provided/isolated environment no longer reaches the dev fixture flag or the Claude/Codex subprocess probes.

Two inline suggestions were posted with the fix.

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.

Checked areas and findings:

  • Imports and namespaces: new modules use namespace subpath imports (effect/Effect, effect/Fiber, effect/Semaphore, effect/Scope, effect/unstable/process/ChildProcessSpawner); no consolidated named imports from "effect". @t3tools/contracts and the pure helper module usageSubscriptionLimits.ts correctly use named imports.
  • Service definition: UsageService keeps its inline Context.Service interface, real make, and export const layer; only subscriptionLimits: [] was added to layerTest. No FooShape type or compatibility shim introduced.
  • Dependency acquisition/runtime boundaries: ChildProcessSpawner, FileSystem, Path, ServerSettingsService and the new Semaphore/Scope resources are acquired with yield* in make; Effect.provideService is used to satisfy sub-effect requirements from those already-acquired instances (consistent with the existing comment at lines 502-503). No ManagedRuntime.make, runPromise, or runPromiseExit was added, and no service instances are passed into production constructors. The only violation is the direct process.env reads noted above.
  • probeCodexRateLimits / probeClaudeUsage (CodexProvider.ts, ClaudeProvider.ts): mirror the existing sibling probes (probeClaudeCapabilities, probeCodexAppServerProvider, checkGrokProviderStatus) including their environment = process.env default parameters, so these were treated as consistent with existing repo patterns rather than new violations.
  • Errors and predicates: no new Schema.TaggedErrorClass, discriminator, or Schema.is predicate was introduced; existing UsageReadError usage is unchanged.
  • File layout: no service module was moved or split; no re-export shims added.
  • Change discipline: behavior change is backed by focused tests (usageSubscriptionLimits.test.ts, usageTranscriptReader.test.ts, usageMerge.test.ts, usageFormat.test.ts, UsagePage.test.tsx), including contract-v4 decode defaults and the read-budget timing paths.
  • Web/mobile files in scope contain React presentation only, with no Effect service usage.

🔍 Agent activity — 17 tool calls

💭 I'm scanning the diff for Effect service convention issues, checking whether the new imports of Effect, Semaphore, and Scope use proper namespace subpaths, and they look fine so far. Now I'm moving on to check the subscription-related service file next. Turning to probeClaudeUsage, I'm checking the dependency acquisition pattern where fileSystem, path, and ChildProcessSpawner are pulled from the environment via yield* and then explicitly provided to a sub-effect—this seems acceptable. Then I'm examining the error-handling signature on the ClaudeProvider probe function, which declares never as its error channel despite internally using Effect.tryPromise and calls that could fail, piped through Effect.result to capture outcomes safely. I also note no new error classes were introduced, so error conventions are mostly not applicable here, and the Layer.succeed test fixture only had a subscriptionLimits: [] field added. I'm now wondering whether direct use of process.env inside the service's make function violates the convention against hiding dependencies in module globals—though this looks like a pre-existing pattern in the codebase rather than something new introduced by this diff, so I want to check how the repo typically handles environment variable access within Effect services. I want to verify import conventions in the actual repo files, checking whether they use named imports from "effect" consistently. Let me check the imports in UsageService.ts specifically.

1. browse_code — apps/server/src/usage/UsageService.ts

2. browse_code — apps/server/src/usage/usageSubscriptionLimits.ts

💭 I notice the new code reaches for process.env directly in a few places rather than going through the existing HostProcessEnvironment service that's already available as hostEnvironment in make. That's inconsistent since there's an Effect service specifically meant to abstract environment access. I want to check what hostEnvironment actually exposes and whether existing UsageService code already relies on it for similar reads.

3. git_tools — hostEnvironment|process.env

4. browse_code — What is HostProcessEnvironment in packages/shared/src/hostProcess.ts? Show its definition (Context.Service?), its interface members, and how server code obtains the process environment through it (e.g. environment field). Also list examples where callers pass process.env directly instead.

💭 I notice hostEnvironment is already acquired at line 387 in make, yet the new code bypasses it and reads process.env directly at four sites instead—exactly the kind of hidden dependency the guidelines warn against, since production code should acquire env values from the yielded service rather than global process state. This also breaks test overrides that rely on injecting hostEnvironment. I should check whether other provider probe call sites elsewhere in the repo pass process.env or hostEnvironment when invoking things like probeClaudeCapabilities or probeCodexAppServerProvider, to see if this pattern is consistent or an outlier.

5. git_tools — HostProcessEnvironment

6. git_tools — probeClaudeCapabilities|probeCodexAppServerProvider|probeCodexRateLimits|probeClaudeUsage

💭 I notice AccountLimitsService.ts exists at the PR head but isn't part of the diff, which means it must already exist in the merge base on main. That's curious since it seems like a usage-limits service that predates this PR—I should check that further. Now I want to look at how ClaudeDriver handles passing environment variables into probes around line 163, since that shows the local convention for this pattern.

7. browse_code — apps/server/src/provider/Drivers/ClaudeDriver.ts

8. browse_code — apps/server/src/usage/AccountLimitsService.ts

💭 I'm confused why the file wasn't found at the PR head tree when the search found it—possibly the search defaulted to main HEAD rather than the reviewed commit, since main may have moved ahead. I'll set that discrepancy aside as it's not central to the task. Let me focus on real convention issues in the diff. I'm flagging that UsageService.make reads process.env directly in several places even though HostProcessEnvironment is already yielded as hostEnvironment in the same scope—this bypasses proper dependency injection and should be fixed to use hostEnvironment instead, since it hurts testability. I'm also looking at probeClaudeUsage in ClaudeProvider.ts, where an AbortController is created outside the Effect context in the function body. I'm considering whether that abort controller could get reused improperly if the effect runs multiple times, comparing it against the existing probeClaudeCapabilities pattern to see if this concern is already handled elsewhere in the codebase.

9. browse_code — apps/server/src/provider/Layers/ClaudeProvider.ts

💭 The probeClaudeUsage pattern matches the existing convention, so that's fine. Now I'm checking CodexProvider's new probeCodexRateLimits and startCodexAppServerProbe for how they handle scope and resources, and whether Effect.scoped is being misused there.

10. browse_code — apps/server/src/provider/Layers/CodexProvider.ts

💭 I'm noticing the probe function defaults to reading process.env and process.cwd() directly, which raises the question of how existing callers like the app server provider supply their environment—I should check how similar patterns elsewhere in the file already handle this before deciding.

11. git_tools — process.env|process.cwd

12. git_tools — process.env

💭 Using process.env as a default parameter matches existing conventions like GrokProvider, so that part is fine. But it's inconsistent that UsageService.make passes process.env explicitly when hostEnvironment is already available in scope - I should check how the Grok home env is handled for comparison. I see it deliberately uses hostEnvironment instead, confirming this inconsistency is worth flagging: swap process.env for hostEnvironment at those four sites. This falls within the dependency-boundary check's scope since reading process.env directly bypasses the existing HostProcessEnvironment abstraction already available in the same function. Now I want to look more carefully at another area - whether UsageService.make's scope requirement from Effect.addFinalizer is compatible with how the layer constructs it, since adding a finalizer means the constructor needs a Scope.

13. browse_code — apps/server/src/usage/UsageService.ts

14. git_tools — export const layer

💭 I notice the line numbers don't match between the two grep results, which makes me suspect the grep defaulted to the main branch rather than the PR head commit referenced by the tree ref. I also see that AccountLimitsService.ts appeared in an earlier grep but doesn't exist at the PR head, which reinforces that git_grep might be searching a different, later revision of main — yet the exact process.env line matches at 171/217/275/284 are hard to explain by coincidence. Let me just directly check the head version of UsageService near its end, and confirm how Layer.effect handles Scope requirements in this repo's Effect v4 setup.

15. browse_code — apps/server/src/usage/UsageService.ts

💭 I'm checking whether Layer.effect in Effect v4 can handle an effect that requires a Scope for finalizers, since the service constructor now uses addFinalizer. Then I'm scanning through other candidate convention violations: the contracts file's namespace import for Effect.succeed looks fine, the UsageService's use of Effect.provideService to inject already-acquired FileSystem/Path/ChildProcessSpawner instances is an intentional pattern (there's even an explanatory comment in the file), and the Semaphore usage looks standard too. I'm also checking whether swallowing errors via catchCause in the settings and refresh paths violates the interruption-preservation convention, but that guidance is specifically about startup reconciliation, and this pattern is already used consistently elsewhere in the file, so it seems fine. The subscription limits module is just pure helpers, not a service, so named imports from it are appropriate rather than a namespace import. I'm also verifying that Effect.fn usage on the probe/await helpers is fine since they live in a non-service module, that the timeout budget passed as a plain number works fine as milliseconds, and that there's no hidden runtime or unwrapped promise leaking outside Effect—the Node reader is properly wrapped in Effect.promise. The one real finding I'm settling on is that UsageService reads process.env directly instead of going through the HostProcessEnvironment dependency the codebase already models and that this same function already yields—that's a legitimate, narrow violation of the "don't hide dependencies in module globals" convention. There's tension between the literal instruction to output exactly "All clear" and the schema requiring valid JSON — I'll resolve it by using "All clear" as the summary content within the required JSON structure if things end up clean. Now I need to decide whether there's actually a finding worth flagging, and I should also check the mobile/web files for similar issues before finalizing. Looking closer at UsageService, I notice it deliberately routes environment reads through HostProcessEnvironment (like the GROK_HOME lookup), yet the new code adds four direct process.env reads within the same service — bypassing that abstraction exactly where it matters, since it makes dev-fixture and provider-probe reads unoverridable in test contexts. That's a concrete, defensible violation of the "don't hide dependencies in module globals" principle. I also see a similar pattern in CodexProvider.ts's probeCodexRateLimits function, which defaults its environment and cwd parameters directly from process global state. I'll consolidate my feedback into a single inline comment covering the affected lines rather than scattering multiple comments, focusing on the probe call sites where this pattern is clearest and easiest to act on. I notice line 387 uses bracket access on hostEnvironment rather than dot notation, likely because of a noPropertyAccessFromIndexSignature rule in tsconfig — so I should suggest bracket-style access for consistency and verify that's actually enforced.

16. git_tools — noPropertyAccessFromIndexSignature

💭 Dot access works fine since that env var isn't a reserved word, so that check passes. I'm also confirming that the hostEnvironment binding on line 154 already existed before this change, which supports my finding that the reference is already bound and the new code bypasses it. Now I'm drafting the actual review comments: one flagging that makeSubscriptionLimitsDevFixture reads process.env directly at lines 171 and 217 instead of using the already-bound hostEnvironment, with inline suggestions matching the exact indentation at each location. The second comment covers lines 275 and 284, where probeClaudeUsage and probeCodexRateLimits are passed process.env directly rather than hostEnvironment — I'll comment on 275 with a suggestion and reference 284 alongside it. For severity, since production behavior is unaffected (both default to process.env), this is really a convention/testability issue rather than a functional bug, so I'm weighing whether it should still count as a failure given it violates the emphasized dependency-acquisition pattern.

17. modify_pr — Effect service conventions: one finding — the new subscription-limits code inside UsageService.make reads the process environment from the module global process.env instead of the HostProcessEnvironment reference the same constructor already binds (hostEnvironment, line 154, used at line 387). Everything else in the diff (namespace subpath imports, make/layer shape, Context.Service tag, dependency acquisition via yield*, no new runtimes or runPromise, no new error classes) matches the conventions.


Agent Credits: 228 credits