Skip to content

fix(usage): address review-bot findings across scan, cache, and page

MacroscopeApp / Macroscope - Effect Service Conventions failed Aug 8, 2026 in 2m 9s

Effect Service Conventions: 1 issue found

Finding

  • packages/contracts/src/usage.ts / apps/server/src/usage/UsageService.ts — UsageReadError declares only reason + free-form detail with no cause, and the settings-read wrap site sets detail: \Server settings could not be read: ${String(cause)}`. Since messageis derived fromdetail, the stringified Cause(arbitrary defect text, stacks, paths) becomes the caller-visible, RPC-serialized message. Add an optionalcause: Schema.Defect()to the error, keepdetaila stable structural description, and pass the underlying failure through ascause`.

Other conventions checked in the new usage code (subpath namespace imports, Context.Service tag with inline interface, exported make/layer/layerTest, dependency acquisition via yield*, no ManagedRuntime/runPromise inside services, namespace import of the service module at consumers in server.ts/ws.ts) look consistent with the repository conventions.

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

  • apps/server/src/usage/UsageService.ts, usageAggregation.ts, usagePricing.ts, usageScanCache.ts, usageTranscriptReader.ts, usageTranscripts.ts
  • apps/server/src/server.ts, ws.ts, server.test.ts, auth/RpcAuthorization.ts
  • packages/contracts/src/usage.ts, rpc.ts, index.ts
  • packages/client-runtime/src/state/server.ts, apps/web/src/state/usage.ts, apps/web/src/usage/*, apps/web/src/components/usage/*

Notes on items deliberately not flagged

  • import { HttpClient, HttpClientResponse } from "effect/unstable/http" and import { ServerConfig } from "../config.ts" match widespread existing usage in apps/server/src, and namespace imports are not a blanket requirement.
  • layerTest living in the production module mirrors ServerConfig/ServerSettingsService; Layer.succeed there is a legitimate test seam.
  • Direct node:fs/node:readline use in usageTranscriptReader.ts is an isolated, documented performance boundary invoked via Effect.promise, not a hidden runtime or fake dependency injection.
  • The multi-value reason discriminator with a generic message is permitted by the conventions (single service-level error with an operation discriminator).
  • Prior-run findings (make not exported, layerTest placement) are resolved at this head commit and were not re-posted.

🔍 Agent activity — 1 tool call

💭 Looking at the diff to verify the prior feedback was addressed — the make export is indeed present with Effect.gen, and layerTest is properly positioned at the module level after the class definition, so both issues appear resolved. Now I'm checking the remaining conventions: the imports look correct with namespace imports for effect modules, but I'm noticing ServerConfig is imported as a named import from config.ts rather than as a namespace. I need to verify whether config.ts is actually a service module and if this violates the convention that local service modules should be imported as namespaces at service boundaries. Looking at the canonical ordering in UsageService.ts, the structure follows imports, schemas, class definition, then layerTest, make, and layer — which mostly aligns with the convention, though layerTest's placement between the tag and make isn't explicitly specified so it's probably acceptable. The error handling is where I'm finding an issue: the UsageReadError is stringifying the cause directly into the detail field, but the convention explicitly warns against adding a detail field that merely copies cause.message, so this needs to be flagged. The reason field acts as a multi-value discriminator here, but the message itself is generic and derived from the operation rather than being chosen based on the reason value — which aligns with the convention allowing a single service-level error with a generic message template. Now looking at dependency acquisition: the effect yields the right services like FileSystem and HttpClient, but readSummary calls NodeOS.hostname() directly and uses Effect.promise with node:fs imports, which bypasses the dependency injection layer. The usageTranscriptReader has a deliberate performance exemption documented with a diagnostics comment, but I'm questioning whether using node:fs directly instead of the FileSystem service is actually justified here. Checking the mutable state — the fileCache and rates are scoped to the make closure, so that's fine per instance. The schema cast with Schema.fromJsonString(RatesCacheFile as unknown as Schema.Codec<...>) is unusual but not explicitly against the conventions. On the web side, the imports look good — using named imports from effect/unstable/reactivity for AsyncResult and Atom. Looking at how UsageReadError is constructed with String(cause) — this loses the actual cause object that catchCause provides, so I need to check if the repo's error conventions preserve the cause field and whether this pattern matches other similar errors in the codebase. Looking at the error handling pattern here, UsageReadError is violating the repo's conventions by putting the stringified cause into detail and then using that in the message, when it should instead preserve the actual underlying error as a cause field and keep detail for structured attributes only. For the ServerSettingsError wrapping, since it's not declared in the RPC's error channel, wrapping is appropriate, but I need to make sure the cause is preserved structurally rather than stringified. The current approach using catchCause with string interpolation is noisy—I should consider whether catchTags would be more precise if getSettings fails with a known tagged error type, though I'd need to verify what error types it actually declares. Looking at the broader error handling patterns, catchCause is used for fallbacks in several places, which does swallow defects and interrupts unintentionally. The convention focuses on using catchTags for statically known failures, but the interrupt-swallowing concern isn't explicitly addressed, so I'll keep the focus on the tagged error handling approach. The rest of the structure checks out: the test layer in the production module follows the repo's pattern, service dependencies are properly acquired from the environment, and readSummary's type signature is correct since the required Path dependency is provided upfront. On the client side, the Atom-based state management in the web app doesn't use Effect services, which is fine. The service imports in the server files are properly namespaced. The detail finding might be the main issue here, though the invalidWindow vs scanFailed discriminator represents two semantically distinct failures — one a validation error, the other a scan failure. Since the message is generic and there's no caller control flow depending on which one occurs, using a multi-value operation discriminator at the service level is acceptable. I'm realizing that passing String(cause) directly into the detail field violates the safety convention—it could leak stack traces and raw configuration paths. Instead, I should add an optional cause field to UsageReadError using the Defect schema, then keep the detail message structural and safe like "Server settings could not be read". Since UsageReadError gets serialized across the wire as an RPC error, I need to be careful about what gets exposed to the client. I'll add two comments: one on the contracts definition suggesting the cause field addition, and one on the UsageService wrap site showing the fix—replacing the catchCause block to pass the cause separately while keeping detail bounded. Since this spans two files, I'll use a unified diff format rather than inline suggestions. I should also check if there are other issues—like whether Effect.catchCause on settingsService.getSettings should be Effect.catchTags instead, but the conventions don't explicitly forbid catchCause for total error handling, so I'll skip that flag. I'm deciding whether to fail the check based on the convention violation I found. Since there's a concrete issue, I'll post comments and mark it as failed. Now I'm drafting the first comment about the UsageReadError in packages/contracts/src/usage.ts—it wraps errors but lacks a cause field, and its only variable property is a detail string that callers stringify into. I'm suggesting we add an optional cause field to preserve the error chain while keeping detail as a stable, bounded description. For the second comment in apps/server/src/usage/UsageService.ts, I'm pointing out that the code currently stringifies the cause into the detail field, which gets serialized over the wire. Instead, I should keep detail structural and pass the cause separately so the error chain survives.

1. modify_pr — One convention issue found in the new usage service: UsageReadError has no cause and the wrap site stringifies the failure cause into its detail field (which is also what message is built from, and is serialized to clients over RPC).


Agent Credits: 147 credits