Repository navigation
Effect Service Conventions: 4 issues found
Four convention violations, all in apps/server/src/cloud/bootServiceWindows.ts:
- Lines 31-39 — The
BootServicetag (and its errors) are pulled in with named imports from./bootService.ts. At this service boundary the module should be imported as a namespace (import * as BootService from "./bootService.ts"), as done inBunPtyAdapter.ts,GitHubSourceControlProvider.ts, andcli/service.ts. - Lines 505-513 — A pure validation failure (percent sign in a path) manufactures
new Error(...)solely to populateBootServiceInstallError.cause. Because that error'smessageis fixed and generic, the guidance never reaches the user, and the failure has no structural attributes. A dedicated tagged error with apathLabelfield is the expected shape. - Lines 528-535 — Same pattern for the "switched off under Startup apps" failure; the test has to assert on
String(error.cause)because the message is not part of the error's structure. - Lines 438-440 —
BootServiceCommandError.stepinterpolates the pid and timeout into free text, while every otherstepin this module and inbootService.ts/pinnedRuntime.tsis a stable stage literal. Variable context belongs in structured attributes with the message derived from them.
Four inline comments were posted on the PR.
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.
Checked areas that raised no findings:
- Effect library modules are imported as subpath namespaces throughout the new module and tests; no consolidated
"effect"named imports. bootServiceWindows.tsfollows the canonical order (imports, pure renderers/helpers,make,layer), exports a realmakeacquiringHostProcessPlatform,HostProcessExecutablePath,FileSystem,Path, andProcessRunner.ProcessRunnerfrom the environment, types operations asBootService["Service"][...], and exportslayerviaLayer.effect. Plainmake/layernames are correct for a concrete implementation module.- No
ManagedRuntime.make,runPromise,runPromiseExit, module-global dependencies, or fakeLayer.succeedimplementations were introduced; the service-instance passing inbootServiceWindows.test.tsis a legitimate test seam, andhost/stopRequestTimeoutare pure configuration. - The lazy
Effect.promise(() => import(...))insideLayer.unwrapincli/service.tskeeps the Windows backend out of the Linux path without bypassing the layer. - No
catchTag/predicate-basedcatchIfrecovery of known tags was added;cli/service.tsalready usesEffect.catchTags. - New shared protocol constants in
serviceProtocol.tsare plain values, correctly imported named. - Tests cover new Windows behavior rather than restating a mechanical refactor, and existing Linux tests/messages were updated in place with no compatibility shims left behind.
🔍 Agent activity — 1 tool call
💭 I'm reviewing the diff to check whether the new bootServiceWindows.ts file follows the Effect service conventions, looking at how it imports from Effect subpaths and shared modules compared to the existing service files. So
BootService.layer(input)in cli/service.ts suggests a namespace import, and in bootServiceWindows.ts they're using named imports for BootService and error classes from the same service module. The convention says to use namespace imports at service boundaries, but this is a bit ambiguous since bootServiceWindows is an implementation module within the same service — error classes and pure helpers can stay as named imports. I'm also noticing a potential issue with error construction: they're wrapping a newly manufactured Error in thecausefield, which violates the guideline about not creating Error objects just to populate cause. I should check how other modules in the repo handle bootService imports and whether this error pattern is consistent elsewhere. on cause. I'm also noticing thatnew BootServiceCommandError({ step: \stopping the running service (process ${pid} did not exit in ...)` })embeds dynamic values like pid and timeout directly into the step string rather than using structured attributes — the convention says to capture stable, serializable domain context and derive the message from those attributes instead of storing unstructured data. This might be a violation, though it's borderline since step is meant to be a stage name. Let me check how step is used elsewhere in bootService.ts to see if this pattern is consistent. The Effect error handling withcatchTags,orElseSucceed,ignore, andoptionlooks fine. Now I'm checking the mapError usage with a _tag ternary check — that's not usingcatchTagsbut rather transforming errors conditionally, which is acceptable sincecatchTagsspecifically applies to catching, not mapping. The environment config withConfig.string("APPDATA")is fine, andprocessIsAliveusingprocess.killin an Effect.sync is imperative but acceptable at the platform level. I'm also looking at cli/service.ts where there's a dynamic import inside a Layer.unwrap with Effect.gen... I'm noticing a tension between the convention of using namespace imports at service boundaries and the practical reality here—bootServiceWindows.ts is importing the BootService tag as a named import from bootService.ts, which would create awkward double-namespacing if I switched to the namespace pattern. The tag itself is used in ways that make the named import cleaner, though technically this is an implementation module crossing a service boundary, so it arguably should follow the convention. I'm noticing that bootServiceWindows.ts introduces two new synthesized error causes that don't exist in the original bootService.ts, and since I'm seeing a pattern whereBootServiceInstallErrormanufactures anErrorjust to store helpful guidance in thecausefield, but since the CLI only prints the genericerror.message, users never see that guidance—this violates convention and creates a real usability gap. There are at least two instances of this same issue where validation or domain failures create errors primarily to populate the cause chain. The proper fix would be to define dedicated tagged error classes with structured attributes and derived messages, then add them to theBootServiceErrorunion in bootService.ts, though that requires multi-file changes. I'm also noticing a third case whereBootServiceCommandErrorinterpolates dynamic values like process IDs and timeouts directly into thestepfield instead of using structured attributes—the convention should be to keep those as separate fields and derive the message from them. The test file imports from bootServiceWindows and pulls in helper functions and constants like parseServiceState and SERVICE_LAUNCHER_PROTOCOL from serviceProtocol, which all look fine. Now I need to check whether Layer.succeed, ManagedRuntime, and runPromise are being used correctly. The launcher script uses raw Node promises since it's a standalone executable rather than an Effect service — that's a pre-existing pattern. The serviceLauncher.ts file has an imperative Launcher class with Promise-based methods, which is also pre-existing; an imperative adapter like this is acceptable when it's clearly named and not an Effect service. The cli/service.ts file wraps dynamic imports with Effect.promise inside Layer.unwrap, which is fine. The test file configures the provider with ConfigProvider.layer and ConfigProvider.fromEnv, which is correct. The new BootServiceKind type addition looks good, and the optional stopRequestTimeout config is just configuration data. The install method type annotation follows the convention of Foo["Service"], and the error construction with step, stdoutLength, and stderrLength is appropriate. Now I'm ready to compile my findings. I've confirmed that moststep` values are stable literal stage names, with line 439 being the only exception where dynamic data gets interpolated—worth flagging in the review. Now I'm preparing four inline comments on the diff, mapping them to the actual line numbers in the new bootServiceWindows.ts file since it's entirely added. Starting with a comment on the import block around lines 31-39 about the namespace import approach. For the percent-sign error around lines 505-513 and the disabled entry error at lines 528-535, I'll comment on the first one with full detail and reference it briefly in the second since they're separate locations but related issues. Then I'll add a comment on line 439 about the step interpolation. I'm keeping each comment concise and actionable. The same pattern
1. modify_pr — Reviewed the new Windows boot-service backend and the touched call sites against the Effect service conventions. Four findings, all in apps/server/src/cloud/bootServiceWindows.ts: the service module is imported with named imports instead of as a namespace, two pure validation failures manufacture an Error only to carry a user-facing message that the generic BootServiceInstallError.message then hides, and one step attribute embeds variable context instead of using structured fields.
Agent Credits: 197 credits