Skip to content

Merge branch 'main' into feat/usage-opencode

MacroscopeApp / Macroscope - Effect Service Conventions failed Aug 14, 2026 in 2m 15s

Effect Service Conventions: 1 issue found

  • apps/server/src/usage/UsageService.ts: the rates -> litellmRates rename left a stale rates shorthand in the UsageAggregator options alongside the new rates: servedRates, producing a duplicate property and an undefined identifier (module will not type-check). Inline comment posted.

No other Effect service convention violations were found in the changed scope.

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 reviewed (in-scope patterns): apps/server/src/usage/UsageService.ts, apps/server/src/usage/UsageService.test.ts, apps/server/src/usage/usagePricing.ts (+ test), apps/server/src/usage/usageScanCache.ts (+ test), apps/server/src/usage/usageTranscriptReader.ts, apps/server/src/usage/usageTranscripts.ts (+ test), apps/server/src/usage/usageAggregation.test.ts, apps/web/src/components/usage/*, apps/mobile/src/features/usage/usageProviders.ts, packages/contracts/src/usage.ts.

Convention checks performed:

  • Imports: Effect modules are imported as subpath namespaces; effect/unstable/http named imports match the existing repository pattern in this module. The prior run's finding about import { make as makeUsageService } has been addressed — the test now uses import * as UsageService from "./UsageService.ts" and yield* UsageService.make.
  • Service definition: Context.Service tag with inline interface, make, then layer order preserved; no FooShape reintroduced.
  • Dependency acquisition: FileSystem, Path, ServerConfig, ServerSettingsService, and HttpClient are acquired from the environment in make. HostProcessEnvironment/HostProcessPlatform are Context.Reference values with defaults, so acquiring them inside resolveOpenCodeDataDir does not leak requirements. NodeOS.homedir() matches the existing NodeOS.hostname() usage and is not covered by the no-global-process-runtime rule (platform/arch only).
  • Runtime boundaries: no new ManagedRuntime.make, runPromise, or runPromiseExit; layerTest remains a static empty-summary seam, and Effect.provideService(HttpClient.HttpClient, ...) in the new test is a legitimate test seam.
  • Errors: no new error classes; existing UsageReadError usage unchanged, with bounded detail and squashed cause preserved.
  • Tests: the new suites cover new backend behavior (models.dev overlay, WAL cache identity, OpenCode message parsing) rather than proving a mechanical refactor.

🔍 Agent activity — 1 tool call

💭 Looking at the PR changes, I see the import alias issue in UsageService.test.ts was already fixed. Now I'm checking the modified files against Effect conventions, particularly how the HTTP client imports are structured — they're using a consolidated named import from the unstable subpath rather than importing as a namespace, which seems to deviate from the convention of importing Effect library modules as namespaced imports. The prior issue is resolved, so now I'm checking other conventions. I notice the import of HostProcessEnvironment and HostProcessPlatform from the hostProcess subpath in UsageService.ts — the convention allows (but doesn't require) namespace imports for service module subpaths when accessing service/tag, make, or layer members. I should check how other files are importing hostProcess to see what pattern is already established. Also, I'm noticing the import ordering on line 39 places the @t3tools/shared/hostProcess import after local imports, which might be worth reviewing. Now I'm looking at the HostProcessEnvironment definition to confirm it's a Context.Service tag, and checking how other modules import it to understand the established pattern. I'm checking how NodeOS.homedir() is being used in the service constructor — it's an imperative host API call that violates the convention against hiding dependencies in module globals or singleton services. There's a lint rule flagging this pattern, and I need to see if this API is already referenced elsewhere in UsageService to understand the scope of the issue. I'm noticing a potential bug where rates is being passed to the UsageAggregator constructor, but it looks like the variable was renamed to litellmRates earlier, which would cause a compile error. I'm spotting a real bug in the head file where there's both a rates: servedRates property and a leftover rates shorthand that references a variable that no longer exists by that name. This causes a TypeScript compile error and creates a duplicate object key, which is definitely in scope for the apps directory even though it's not strictly an Effect service convention issue. I should flag this since the added rates: servedRates line on 477 directly causes the stale rates on line 480 to become problematic. Now moving on to check for actual Effect service convention violations — looking at resolveOpenCodeDataDir, it's using NodeOS.homedir() directly rather than going through an Effect service. I checked the available services and there's no HostProcessHomeDirectory option, but NodeOS.hostname() is already used elsewhere in the code, so this approach is consistent with what's already there. For the rest of the review: the type cast in readFileRecords isn't an Effect convention issue, the test file properly uses provideService for seaming, and the HTTP imports match the existing repo pattern. But there's a potential type mismatch — UsageService.make calls resolveOpenCodeDataDir which requires HostProcessEnvironment | HostProcessPlatform, and this requirement propagates through readSummary, yet the service interface declares Effect.Effect<UsageSummary, UsageReadError> with no environment requirements. This would likely surface as a type error when wrapping with Layer.effect. Actually, thinking through this more carefully — readSummary would have type Effect.Effect<UsageSummary, UsageReadError, HostProcessEnvironment | HostProcessPlatform>, and assigning that to the interface's expected signature (with R = never) wouldn't work. The convention here is that production service construction should acquire HostProcess dependencies at the make level using yield* so those requirements bubble up to the layer itself rather than hiding inside the service value. Looking at the existing pattern in the file with resolveTranscriptDirs, it properly acquires its dependencies in make and exposes them through the layer. The HostProcess services should follow the same approach — acquiring HostProcessEnvironment and HostProcessPlatform at the top level so readSummary stays context-free, which aligns with the comment already in the code. This is both a type correctness issue and a convention deviation that needs fixing. But now I'm wondering if the test actually works — the test provides HostProcessEnvironment but not HostProcessPlatform, and if make required the platform it would fail. Since the requirement is buried inside readSummary and called within the same provided context, the test passes, but this suggests neither NodeServices.layer nor the default layers in hostProcess.ts provide HostProcessPlatform. Let me trace through what's actually being provided. The Context.Reference values with defaults mean R is never, so there's no type leakage or convention issues. I'm checking whether the no-global-process-runtime lint rule covers NodeOS.homedir() since resolveOpenCodeDataDir uses it, though there's a more pressing issue—I need to handle that leftover rates, comma. Looking at lines 473-481 in the head file, I can see both rates: servedRates, and rates, are present, confirming a duplicate or undefined identifier. Let me search the file for all rates declarations to understand what's in scope—I know there's litellmRates at line 153 and openCodeRates at line 162, so I need to verify if rates itself is defined somewhere. The leftover rates, duplicate is definitely breaking the compile. Looking at the other files in the diff, usagePricing.ts has straightforward pure helpers with named exports which is fine, and usageTranscriptReader.ts contains async functions that handle node:sqlite operations and return null on errors—it's an imperative reader module invoked through Effect.promise rather than Effect service code, so the error handling conventions don't apply the same way. The test file properly uses Effect.provideService as a test seam, and the namespace imports follow the right pattern. The wal field addition is fine too. Nothing else in the PR violates the Effect conventions. TypeScript will error on duplicate object keys regardless, and rates being undefined compounds that. This is technically outside the convention check's scope, but it's a concrete build break in the changed code — I should report it clearly and mark the check as failing since it's a defect within the scope of what was modified. Now I need to verify the other files in the diff for convention compliance: checking whether the imported icons like OpenCodeIcon actually exist in the Icons module, and confirming the mobile file has its equivalent PROVIDER_MARK constant. I need to post a review comment on line 477 of the UsageService file, pointing out that rates was renamed to litellmRates but the aggregator is still using the old variable name. Since I can only suggest changes within the diff range, I'll use a unified diff format in the comment to show what needs to be removed.

1. modify_pr — One finding in apps/server/src/usage/UsageService.ts: the rate-table rename left a stale rates shorthand in the UsageAggregator construction. No Effect service convention violations found in the rest of the changed scope (namespace imports, service tag/make/layer order, dependency acquisition, and test seams all look consistent with the conventions).


Agent Credits: 97 credits