Skip to content

docs(usage): add Usage page after screenshot for PR

MacroscopeApp / Macroscope - Effect Service Conventions failed Aug 10, 2026 in 8m 36s

Effect Service Conventions: 1 issue found

1 finding

  • apps/server/src/usage/usageCursor.ts (lines 370-410): refreshAccessToken and fetchCsvOnce perform outbound HTTP with the global fetch plus AbortSignal.timeout, and are reached from UsageService.readSummary through Effect.promise(() => fetchCursorUsageRecords(...)). The HTTP dependency is therefore hidden from the Effect environment even though UsageService.make already acquires HttpClient.HttpClient for the LiteLLM rate table, so the Cursor fetch and its 401/403 refresh path cannot be provided a test client, traced, or interrupted. Suggested fix: return an Effect requiring HttpClient.HttpClient (built with HttpClientRequest, deadline via Effect.timeout) and yield it from readSummary.

An inline review comment was posted for this finding.

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.

Scope examined (include patterns only): apps/server/src/usage/UsageService.ts, usageCursor.ts, usageOpenCode.ts, usagePricing.ts, usageScanCache.ts, usageTranscriptReader.ts, usageTranscripts.ts plus the new tests, apps/web/apps/mobile usage provider maps, packages/contracts/src/usage.ts, packages/shared/src/usageMerge.test.ts.

Checked and clean

  • Imports: UsageService.ts continues to use namespace imports from effect/* subpaths; the new local modules are imported as named pure helpers (fetchCursorUsageRecords, listOpenCodeDatabases, parseOpenCodeMessageData, …), which the conventions explicitly allow for pure helper/schema modules. No consolidated from "effect" imports were introduced.
  • Service definition: Context.Service tag with inline interface, real make, and export const layer = Layer.effect(UsageService, make) are unchanged; no FooShape type or compatibility shim was added, and no file moves occurred.
  • Dependency acquisition: no service-instance parameters, ManagedRuntime.make, runPromise, or runPromiseExit were added. layerTest remains a legitimate empty test layer.
  • Errors: no new error classes; UsageReadError construction sites are unchanged, and the per-source status/message strings are the existing UsageSource contract shape rather than an Effect error channel.
  • Change discipline: new tests target the new pure parsers (parseCursorUsageCsv, parseOpenCodeMessageData, parseGrokLine, pricing aliases), which is proportionate to the backend behavior change; existing comments and invariants were preserved while the scan loop was restructured.

Context supporting the finding: apps/server/src/usage/usageCursor.ts holds the only fetch( call sites in apps/server/src; other outbound HTTP (AnalyticsService.ts, BitbucketApi.ts, cloud/http.ts, UsageService.ts itself, packages/shared/src/httpReadiness.ts) acquires HttpClient from the Effect environment, and tsconfig.base.json raises the globalFetch/globalFetchInEffect diagnostics to error.


🔍 Agent activity — 1 tool call

💭 I'm looking at the Effect service conventions and spotting a violation in usageCursor.ts — it's making network calls with raw fetch instead of pulling HttpClient from the Effect environment like the rest of the service does. The convention is clear that dependencies should come from the environment, not from module-level singletons or closures. The codebase already uses Effect.promise wrappers around promise-based helpers, so that pattern is established, though it does swallow failures as defects which isn't ideal. For the FileSystem side, readCursorAuthTokens uses synchronous Node APIs, but I'm seeing that raw Node fs usage is already baked into helper functions like readDirectoryVolumeId and listTranscriptFiles, so there's precedent for that approach in this module. On errors, the Cursor integration reports status and message strings through the UsageSource contract rather than typed Effect errors — that's the existing contract shape, not a new error channel. The convention about keeping failures typed in Effect applies when callers need fallback behavior, but that's not quite the situation here. Checking the imports now — usageCursor.ts and usageOpenCode.ts don't import any Effect modules, and UsageService.ts imports namespaces properly, so no violations there. But there's a real issue with the network call: the Cursor path uses raw fetch inside a Promise helper wrapped with Effect.promise, bypassing the HttpClient service that the codebase already models in the Effect environment. This means the code escapes tracing, retry policies, and interruption handling — it's hiding a service dependency in a module global, which violates the convention that production services should acquire their dependencies from the environment.… I'm noticing that usageCursor.ts is the only place in the server using raw fetch directly, which bypasses the HttpClient that's already available in the Effect environment. The service declares this dependency but the helper function wraps the fetch call in Effect.promise, which isn't ideal for a function that can't actually fail. This is a clear convention violation — the HTTP dependency is hidden in a module global rather than properly acquired through the Effect environment, making it untestable. The same file uses HttpClient for LiteLLM, so this inconsistency stands out. There's also a token-mutation side effect through writeStateValue that persists to SQLite, though that follows the established pattern. I don't see new error classes or issues with the Effect.promise usage itself, but I should note that usageOpenCode.ts has synchronous SQLite reads wrapped in Effect.sync that could block the fiber if the database is large.… I'm noticing a discrepancy between what grep found at line 222 and what I actually saw in the file around line 249-264. The grep search matched the process.env[ pattern, but the line content doesn't match what I viewed earlier — this is strange.… The grep confirmed the main branch already has a Grok environment variant, so that's not a new discovery. Looking at the findings now — the first issue I'm identifying is in usageCursor.ts, which makes HTTP calls through the global fetch function wrapped in a Promise helper connected to UsageService via Effect.promise, even though the service already has HttpClient.HttpClient available from the environment. This is a hidden dependency that bypasses the Effect environment entirely, breaking the abstraction boundary for HTTP communication. The second candidate is about error shapes — Cursor failures return { status, message } strings matching the UsageSource contract, so that's the expected wire format. The error messages propagate to callers, which is acceptable. The third point is that Effect.promise on fetchCursorUsageRecords already swallows errors internally, which is fine. The fourth issue involves readCursorAuthTokens and readOpenCodeDatabaseRecords performing synchronous blocking I/O through Node fs and sqlite inside Effect.sync —arguably hiding imperative APIs, but the conventions don't explicitly target this pattern, and the module already uses node fs directly elsewhere for reading transcripts, so it's consistent with established practice. The real question is whether the raw fetch itself constitutes a clear enough violation to fail the check, since the default is failure and a single clear violation should trigger it. The fetch does bypass the HttpClient service that's already modeled in the service, which feels like a designed-in finding for this PR—the service has the dependency available, so either passing it through or wrapping the fetch in an Effect using HttpClient would be the right move. I also notice fetchCursorUsageRecords writes to the state DB as a side effect, which is imperative but acceptable. Looking at test coverage, there's a layerTest for UsageService, but the cursor fetch path isn't tested—only the pure parsers are. The convention is to use test implementations for external services, and since the backend behavior changed substantially with new providers, focused tests were added for the parsers. But there's no test covering the HTTP path because it's untestable without HttpClient, which actually supports the finding. I should verify the Effect version and how HttpClient is used in the service to suggest an accurate fix—the service calls httpClient.get(url).pipe(...), so for the cursor path I'd need to handle headers and query params, probably by making fetchCursorUsageRecords an Effect that requires HttpClient or accepts the request as a parameter. Now I'm planning where to add inline comments in the diff. The new usageCursor.ts file has the fetchCsvOnce function around lines 392–410, with the raw fetch call at line 402. I'll add a comment covering that region to explain the testing gap, and possibly another comment on the UsageService.ts file around line 644.… The repo has a globalFetch: error diagnostic that would flag any use of fetch() unless suppressed, and the new file only disables nodeBuiltinImport, leaving globalFetch unchecked. Since usageCursor.ts calls Date.parse(), I need to verify whether there's also a globalDate: error diagnostic that would catch that usage.… The main issue is that usageCursor.ts is using the global fetch function directly along with AbortSignal.timeout, which bypasses the HttpClient service that UsageService already depends on. This creates a dependency that isn't represented in the Effect environment, can't be tested with a mock HttpClient, and violates the repo's globalFetch: error rule. There's also a second potential finding starting to emerge around writeSt.…

1. modify_pr — Effect service conventions review of the new usage providers. One finding: the Cursor path performs outbound HTTP with the global fetch instead of the HttpClient service that UsageService already acquires from the environment, so that dependency is hidden from the Effect environment (no test layer, tracing, or interruption). Everything else in the diff (namespace imports of effect/* subpaths, Context.Service + make + layer shape, named imports for the pure usageOpenCode/usageTranscripts/usagePricing helpers, contract/scan-cache widening) matches the conventions.


Agent Credits: 274 credits