Repository navigation
Effect Service Conventions: 1 issue found
Finding
packages/contracts/src/usage.ts/apps/server/src/usage/UsageService.ts—UsageReadErrordeclares onlyreason+ free-formdetailwith nocause, and the settings-read wrap site setsdetail: \Server settings could not be read: ${String(cause)}`. Sincemessageis derived fromdetail, the stringifiedCause(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.tsapps/server/src/server.ts,ws.ts,server.test.ts,auth/RpcAuthorization.tspackages/contracts/src/usage.ts,rpc.ts,index.tspackages/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"andimport { ServerConfig } from "../config.ts"match widespread existing usage inapps/server/src, and namespace imports are not a blanket requirement.layerTestliving in the production module mirrorsServerConfig/ServerSettingsService;Layer.succeedthere is a legitimate test seam.- Direct
node:fs/node:readlineuse inusageTranscriptReader.tsis an isolated, documented performance boundary invoked viaEffect.promise, not a hidden runtime or fake dependency injection. - The multi-value
reasondiscriminator with a generic message is permitted by the conventions (single service-level error with an operation discriminator). - Prior-run findings (
makenot exported,layerTestplacement) 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
makeexport is indeed present withEffect.gen, andlayerTestis 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 noticingServerConfigis 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: theUsageReadErroris 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 withSchema.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 howUsageReadErroris constructed withString(cause)— this loses the actual cause object thatcatchCauseprovides, 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,UsageReadErroris violating the repo's conventions by putting the stringified cause intodetailand then using that in the message, when it should instead preserve the actual underlying error as acausefield and keepdetailfor structured attributes only. For theServerSettingsErrorwrapping, 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 usingcatchCausewith string interpolation is noisy—I should consider whethercatchTagswould be more precise ifgetSettingsfails 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,catchCauseis used for fallbacks in several places, which does swallow defects and interrupts unintentionally. The convention focuses on usingcatchTagsfor 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, andreadSummary's type signature is correct since the requiredPathdependency 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. Thedetailfinding might be the main issue here, though theinvalidWindowvsscanFaileddiscriminator 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 passingString(cause)directly into the detail field violates the safety convention—it could leak stack traces and raw configuration paths. Instead, I should add an optionalcausefield 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 thecausefield 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 whetherEffect.catchCauseonsettingsService.getSettingsshould beEffect.catchTagsinstead, 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 theUsageReadErrorin packages/contracts/src/usage.ts—it wraps errors but lacks acausefield, and its only variable property is adetailstring that callers stringify into. I'm suggesting we add an optionalcausefield to preserve the error chain while keepingdetailas 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 thedetailfield, which gets serialized over the wire. Instead, I should keepdetailstructural 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