Repository navigation
Effect Service Conventions: 3 issues found
Three convention violations were found in the changed Effect service code and posted as inline review comments.
apps/server/src/usage/UsageService.ts(lines 464-470, 505): uses a newly importednode:pathinside the service even thoughmakealready acquiresPath.Pathaspath; this also forces the new file-wide// @effect-diagnostics nodeBuiltinImport:offsuppression and breaks the documented invariant that raw Node filesystem/path access stays isolated inusageTranscriptReader.ts.apps/server/src/usage/usageOpenCodeDb.ts(lines 34-42): readsprocess.env.OPENCODE_DATA_DIR/XDG_DATA_HOMEfrom a module global, hiding an environment dependency thatUsageService.makealready models viaHostProcessEnvironment(used there for the analogousGROK_HOMElookup). Thread the env value in from the service instead.apps/server/src/provider/Layers/OpenCodeAdapter.ts(lines 1247-1265): theturn.completedpayload is built asRecord<string, unknown>and emitted withpayload: completedPayload as never, discarding type checking against theTurnCompletedPayloadcontract and assigning an explicitmodelUsage: undefinedfor an optional field. Spread only defined keys, asClaudeAdapter.completeTurndoes.
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.
Methodology: compared the PR head against the merge base for all TypeScript files in scope (apps/**, packages/**), then read the surrounding service modules to establish existing conventions: UsageService.make (acquires FileSystem, Path, ServerConfig, ServerSettingsService, HttpClient, HostProcessEnvironment), usageTranscriptReader.ts (deliberate, documented Node-fs isolation for streaming performance), packages/shared/src/hostProcess.ts (HostProcessEnvironment as a Context.Reference), packages/contracts/src/providerRuntime.ts (TurnCompletedPayload, ThreadTokenUsageSnapshot), and the Claude/Codex adapters for comparable emission sites.
Checked and not flagged:
- Promise-returning helpers in
usageOpenCodeDb.tsconsumed withEffect.promisemirror the pre-existing, deliberately isolatedusageTranscriptReader.tspattern; thenode:fs/node:sqliteuse in that dedicated boundary module (with its own suppression comment) is consistent with the repository's existing approach. - No
ManagedRuntime.make,runPromise,runPromiseExit, service-instance parameters, or newLayer.succeedfake-DI were introduced. - No new error classes, predicates, or
catchTag/catchIfusage in the diff;Effect.fnspan names match the surrounding file's style. - The pre-existing
OpenCodeAdapterShapestandalone interface predates this PR and was not flagged as unrelated legacy. - Contract/version, mobile/web provider registry, docs, and test updates are mechanical additions of the
opencodeprovider kind and need no further changes.
🔍 Agent activity — 12 tool calls
💭 I'm scanning the changed files for Effect service convention violations, focusing on the new usage module that uses async Promise-based functions with node:fs directly, and checking whether wrapping that helper in Effect.promise within UsageService.ts counts as a violation of the expected pattern. I want to check whether the file uses the platform FileSystem service or if it suppresses the nodeBuiltinImport diagnostic file-wide instead, which would be a notable finding versus using the proper Effect abstraction. I also want to verify whether
readOpenCodeDbRecordsreturns null on failure rather than surfacing a typed error, which would violate the convention of keeping failures typed in Effect.
1. browse_code — apps/server/src/usage/UsageService.ts
2. browse_code — apps/server/src/usage/usageOpenCodeDb.ts
💭 I'm checking whether the merge base version already had node:os imported and the diagnostics suppression comment, since the diff seems to be adding the
nodeBuiltinImport:offmarker fresh at the top of the file while node:os was already present before. I also want to check the existing usageTranscriptReader.ts to see if it's Effect-based with FileSystem or uses raw promise helpers, since that tells me what pattern the new module should follow.
3. browse_code — apps/server/src/usage/usageTranscriptReader.ts
4. browse_code — apps/server/src/usage/UsageService.ts
💭 I'm noticing the existing pattern keeps raw node:fs isolated to usageTranscriptReader.ts while UsageService stays on Effect's FileSystem, but this PR adds node:path/node:os imports plus a file-wide suppression to UsageService.ts, which breaks that stated invariant. I also want to check whether merge base already imported node:os without suppression, and whether the diagnostic actually tolerates certain builtins — and since UsageService already has Effect's Path service injected, using NodePath.join instead is a real convention violation worth flagging. Let me look at the surrounding UsageService code more closely to confirm.
5. browse_code — apps/server/src/usage/UsageService.ts
💭 I'm noticing UsageService.ts breaks the established convention by adding a node:path import and suppression comment when it already has access to Effect's Path and FileSystem services—the module should use path.join/path.dirname from the injected Path service instead, consistent with how usageTranscriptReader.ts isolates FileSystem usage. Checking usageOpenCodeDb.ts next: it's a new module doing filesystem and sqlite access as raw async functions that return null on failure, then wrapping them in Effect.promise—which swallows the typed errors that Effect's error model is meant to preserve. Actually the existing usageTranscriptReader already does the same null-return pattern with justified perf comments, so this new module just mirrors an existing deliberate choice rather than introducing a new issue. But it also reads process.env.OPENCODE_DATA_DIR directly, while the codebase already has a HostProcessEnvironment service used elsewhere for env access—that's a real convention violation, hiding a dependency in a module global instead of acquiring it from the Effect environment. Separately, in OpenCodeAdapter.ts I'm noticing a
payload as nevercast that bypasses schema typing on an emitted event, plus assigning raw OpenCode token data into a field that presumably expects a different usage shape—likely a real contract bug, though I'm unsure it falls under the specific conventions I'm supposed to check. I want to verify what the turn.completed event type actually expects before flagging this.
6. browse_code — In apps/server/src/provider/Layers/OpenCodeAdapter.ts, what is the type of the event emitted with type "turn.completed"? Show the schema/type definition of the turn.completed payload (fields like state, usage, modelUsage, totalCostUsd) from the contracts package, and how other adapters (e.g. ClaudeAdapter, CodexAdapter) build that payload. Also show the type of "thread.token-usage.updated" payload.
7. browse_code — Show the definition of HostProcessEnvironment in packages/shared/src/hostProcess.ts (is it a Context.Service?), and list places in apps/server that read process.env directly versus using HostProcessEnvironment. Is there a lint rule or convention forbidding direct process.env access in server services?
💭 I'm noticing that UsageService already relies on
hostEnvironmentfor GROK_HOME instead of reading process.env directly, so this new code's module-global helper reading process.env may violate the convention against hiding dependencies in module globals rather than sourcing them from the injected environment. I'm also weighing whether theas nevercast is a legitimate typing discipline issue: it seems to hide an incompatibility where an optional payload field is explicitly set to undefined, and casting toneverbypasses type checking at a service boundary — this looks like a fair boundary typing violation worth flagging with a concrete fix. For the raw usage shape being passed through Schema.Unknown, that's acceptable since it's SDK-native and provider-agnostic. Now I'm checking whether the new NodePath usage in UsageService, along with a freshnodeBuiltinImport:offsuppression comment, is consistent with how other files in the repo handle that same lint rule — I need to grep for existing usages to confirm.
8. git_tools — nodeBuiltinImport
9. git_tools — nodeBuiltinImport|effect-language-service|languageServicePlugins
💭 I'm noticing node:os passed the lint check at merge base without suppression, which makes me wonder if the rule only flags specific builtins that have Effect platform equivalents like node:fs, node:path, or node:child_process, rather than all node imports. That confirms finding #1: UsageService.ts should use the already-acquired
pathservice instead of importingnode:pathdirectly, dropping both the new import and the file-wide diagnostic suppression. I'm also noting that usageTranscriptReader deliberately isolates raw fs access so the rest of usage code stays on Effect FileSystem. For finding #2, usageOpenCodeDb.ts readsprocess.envdirectly instead of using theHostProcessEnvironmentreference UsageService already relies on for GROK_HOME, creating a hidden module-global dependency that could break env overrides in tests — the fix would be threading env through as a parameter toresolveOpenCodeDbPath. I'm weighing whether this is too strict given the check's guidance against hiding dependencies in module globals or closures over singleton services. Finding #3 ispayload: completedPayload as never, which bypasses theTurnCompletedPayloadtyping and explicitly setsmodelUsage: undefined— a solid finding since it defeats the schema contract at a service boundary, with the fix being a typed payload built with conditional spreads similar to ClaudeAdapter. I'm also checking naming conventions forEffect.fnspans across the codebase, comparing how UsageService names its spans versus what OpenCodeAdapter uses, to see if this file's span name is consistent. I noticed filesystem access via node:fs in usageOpenCodeDb mirrors the deliberate isolation pattern in usageTranscriptReader, justified by performance, so I'm not flagging that. I also spotted thatreadOpenCodeDbRecordsswallows failures by returning null for both unavailable sqlite and errors, causing the service to misreport a failed DB read as "ok" with skippedFiles=1 — a real correctness bug, but since it's outside the Effect conventions scope I'm checking, I'll skip it rather than flag it here.
10. git_tools — Effect.fn(
11. browse_code — apps/server/src/provider/Layers/OpenCodeAdapter.ts
💭 The Effect.fn naming looks fine, and the standalone shape interface in the adapter file is pre-existing legacy so I won't flag it. Now I'm drafting inline comments, starting with one about the NodePath.join usage and import suppression around line 464 of UsageService.ts. I'm working out the diff suggestion for replacing NodePath.join/dirname with path.join/dirname across both hunks (lines 464-470 and 505), plus removing the now-unused import and diagnostic suppression once both are converted. I need to make sure the line numbers reference the RIGHT side of the diff for the review tool. For the second file, usageOpenCodeDb.ts, I'm proposing changing the resolver function to accept an env parameter rather than reading directly from process.env, so callers pass hostEnvironment explicitly—this keeps consistency with how the codebase avoids hidden dependencies on module globals. I'll present a small diff and note the caller update needed. For the third issue, I'm looking at the
payload: completedPayload as nevercast in OpenCodeAdapter and preparing an inline suggestion for that line. For the OpenCodeAdapter usage/cost payload, I need to restructure the code so lastCost is read before it gets reset, building both usagePayload and costPayload upfront before the reset happens. I'm drafting the replacement lines with correct indentation to keep usage and cost captured before clearing state for the next turn. That line is too long, so I'm reformatting the ternary across multiple lines to stay under the column limit, keeping the modelUsage logic intact since it's typed as a generic record. Now I'm figuring out how to determine the final state for the findings. Since the check defaults to failure whenever there are findings, and both the node:path issue and theas nevercast are clear convention violations, I'll go with failure and post the review with the three comments.
12. modify_pr — Effect service conventions review of the OpenCode usage integration. Three findings: a Node builtin used in place of the Path service already acquired by UsageService.make, a module-global process.env read for a dependency this service already models via HostProcessEnvironment, and a as never cast that removes contract typing from the turn.completed payload.
Agent Credits: 270 credits
Token usage: 56 uncached input · 966,179 cache-read input · 221,049 cache-write input · 28,058 output
Agent Credits may also include non-token charges from external tools such as web research.