Repository navigation
Effect Service Conventions: 2 issues found
Two convention violations in newly added server code, reported inline:
apps/server/src/provider/acp/GenericAcpSupport.ts—makeGenericAcpRuntimeaccepts aChildProcessSpawner.ChildProcessSpawner["Service"]instance and re-provides it viaLayer.succeed, so an owned Effect dependency is injected as a value and kept out of the returnedEffect's requirements. Production callers (GenericAcpAdapter.startSession,AcpRegistryProvider.discoverModelsViaAcp) acquire the spawner from the environment only to pass it back in.apps/server/src/provider/Layers/GenericAcpAdapter.ts(lines 626 and 941) —ProviderAdapterProcessError/ProviderAdapterRequestErrorare constructed withdetail: cause.message, so the wrapper's caller-visiblemessageis derived from the cause instead of stable structural attributes.
Note (not commented inline): apps/server/src/provider/Drivers/AcpRegistryDriver.ts builds ProviderDriverError.detail from cause.message ?? String(cause). It mirrors the existing drivers, but new code should use a fixed structural detail and preserve the failure only as cause.
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 in scope: apps/server/src/provider/Drivers/AcpRegistryDriver.ts, apps/server/src/provider/Layers/AcpRegistryProvider.ts, apps/server/src/provider/Layers/GenericAcpAdapter.ts, apps/server/src/provider/acp/GenericAcpSupport.ts, apps/server/src/provider/acp/AcpRegistryCatalog.ts, apps/server/src/provider/acp/AcpSessionRuntime.ts, apps/server/src/provider/builtInDrivers.ts, apps/server/src/ws.ts, apps/server/src/auth/RpcAuthorization.ts, packages/contracts/src/{acpRegistry,rpc,settings,model,index}.ts, and the touched web/mobile modules.
Checked and found acceptable:
- Namespace subpath imports for Effect modules; named imports retained for
@t3tools/contracts, schemas, and pure helpers. Effect.service(ServerConfig)/yield* Crypto.Crypto/yield* ChildProcessSpawner.ChildProcessSpawneracquisition inside the driver and adapter constructors;HttpClientacquired from the environment inlistAcpRegistryCatalog.- No
ManagedRuntime.make,runPromise, orrunPromiseExitintroduced in domain/service code. Effect.provideService(Crypto.Crypto, ...)/Effect.provideService(Scope.Scope, sessionScope)in the driver and adapter are legitimate scope/context plumbing for effects handed to the managed-provider machinery.- Error channel handling uses
Effect.catch/Effect.catchCausewhere the whole channel is intentionally handled; nocatchTag/predicate-based recovery of known tags was introduced. - New tests exercise real behavior (snapshot states, ACP prompt flow against the mock agent, catalog merging, launch-arg parsing) rather than restating a mechanical refactor.
Search performed for the risk patterns named in the check: service-instance parameters (["Service"] in factory inputs), Layer.succeed, ManagedRuntime.make, .runPromise/.runPromiseExit, and detail: cause.message.
🔍 Agent activity — 1 tool call
💭 Looking at the diff to identify where the Effect service conventions are being violated across these provider files. Checking the import patterns — the code uses both
import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"and consolidated named imports from barrel exports, which aligns with the repo's existing patterns. The key issue appears inGenericAcpSupport.tswheremakeGenericAcpRuntimeaccepts aChildProcessSpawner.ChildProcessSpawner["Service"]parameter and then constructs a layer from it, which suggests the service is being passed in rather than properly layered through the Effect runtime. I'm noticing thatdiscoverModelsViaAcprelies onprocess.envandprocess.cwd()directly instead of injecting them as services, which hides these dependencies in Node globals rather than making them explicit in the Effect environment. This might be an existing pattern in the codebase, but it still violates the principle of not concealing dependencies. Then there's the error handling inProviderDriverErrorthat seems to be cutting off mid-violation. Looking at the error construction patterns, I'm seeing a consistent issue: multiple places are deriving thedetailfield directly fromcause.messageand then using that to build the wrapper error message. This happens inGenericAcpAdapterwithProviderAdapterProcessError, in thefileSystem.readFileerror handling withProviderAdapterRequestError, and it looks like there's another instance starting withEffectAcpErthat got cut off. All of these violate the rule about not copyingcause.messageinto the detail field. Moving past the error construction violations, I'm checking the other patterns: the error normalization helpers likemapAcpCallbackFailurelook fine, the Effect error handling withcatchandcatchCauseis appropriate, and now I'm examining whetherGenericAcpAdapterand its factory functionmakeGenericAcpAdapterfollow the service module conventions properly. It's not a Context.Service, just an adapter factory that returns aProviderAdapterShape, which seems consistent with how other adapters likeCodexAdapterare structured, but I should verify that pattern. TheAcpRegistryProvider.tsfile placement in theLayers/directory matches the existing convention for providers. I'm also looking at howLayer.buildis used insidemakeGenericAcpRuntimeto inject theChildProcessSpawnerservice, and how the session setup explicitly provides theCryptoandScopeservices — that's a valid approach even if slightly unconventional. Now I'm checking how the provider instances are being merged together. Looking at the contracts inacpRegistry.tsand… I'm noticing that the new AcpRegistryDriver follows the same pattern as CursorDriver by including adetailfield that copiescause.message, which is an established convention in the codebase. But the linting rule explicitly prohibits adding adetailfield that merely duplicatescause.message, so this is a concrete violation that needs to be flagged even though it mirrors existing code. For GenericAcpSupport, thechildProcessSpawnerservice injection pattern matches CursorAcpSupport and GrokAcpSupport exactly, and the rule is clear that this type of implementation dependency injection should be flagged regardless of whether it's an established pattern elsewhere in the repo. The injection itself is actually avoidable — instead of providing the spawner directly, they could just addChildProcessSpawnerto the return type requirements and let the caller handle it, which would be cleaner. Now I'm checking other potential issues like the naming conventions for adapter factories to see if there are other violations worth flagging.… Theprocess.envandprocess.cwdpatterns are already established elsewhere in the codebase like GrokProvider, so that's not a new issue. Thedetail: cause.messageapproach is also widespread, but I'm concerned about the risk here—the check should flag concrete violations that are either introduced or kept in the PR's changed scope. The new code in AcpRegistryD does add a detail… The issue is that these error classes require adetailfield by design, so when wrapping lower-level errors, the code currently copiescause.messageintodetail. But the convention forbids this pattern. The fix is to provide meaningful structural context in the detail string instead—something like describing what operation failed rather than just echoing the underlying error message. I'm spotting a few instances of this in the new code: AcpRegistryDriver uses a template literal withcause.message, and GenericAcpAdapter does it in both ProviderAdapterProcessError and ProviderAdapterRequestError. There's also a case where raw wire payload gets encoded into a runtime event detail, which violates the convention against copying raw payloads into detail fields—though I should check if existing code already follows this pattern. Now I'm considering whether the GenericAcpSupport service injection itself is worth flagging as a structural concern. rEffect.succeedin withDecodingDefault, which is fine. The schema imports likeTrimmedNonEmptyStringfrom baseSchemas are allowed. Checking the web files, the named imports from@t3tools/contractsin AddProviderInstanceDialog.logic.ts are also acceptable. The existing pattern of importingChildProcessSpawnerfrom effect's unstable process module in AcpRegistryDriver matches what CursorDriver already does, so I'll skip that. GenericAcpAdapter correctly usesEffect.serviceto access ServerConfig, and I need to verify there aren't any helpers being added that just wrap error construction. Looking at the unsupported text generation helper — it's a small wrapper used multiple times that adds shared detail, which seems reasonable for reuse. ThemapAcpCallbackFailurenormalization wrapper is also fine. Checking AcpRegistryCatalog, thelistAcpRegistryCatalogfunction properly uses HttpClient from the environment without error modeling issues.… I'm seeing that this pattern shows up across three existing modules, so it's genuinely pervasive. The tricky part is that GenericAcpSupport's service injection approach is technically defensible—it's in the changed scope and follows the instructions about flagging when older code differs. But it's also just copying what the rest of the codebase does, and the guidelines explicitly say not to demand unrelated repository-wide cleanup or fail on legacy style preferences. I'm leaning toward reporting it since the new module is in changed scope, but I need to think through whether that's the right balance here. The core question is which findings I actually want to stand behind. GenericAcpSupport acceptingchildProcessSpawner: ChildProcessSpawner["Service"]and providing it viaLayer.succeedis a clear rule match—ChildProcessSpawner is an Effect service that AcpSessionRuntime.layer requires, and callers do yield it directly. I'll flag it with a concrete suggestion to drop the field and let the requirement flow through the Effect signature instead, since that mirrors how the pattern should work. For thedetail: cause.messageinstances across GenericAcpAdapter and AcpRegistryDriver, the rule explicitly calls this out as problematic. I'm seeing three near-identical clones of existing repo idiom, which makes me hesitant, but the rule is clear—don't copy cause.message into detail and then use it to construct the wrapper message. I'll report all three concisely with inline comments on the new lines in the diff, though I'm reconsidering the strength of this finding since the convention in the codebase seems to derive the message from structural attributes rather than just copying the cause. I'm settling on reporting three inline findings: the service injection issue in GenericAcpSupport.ts and the twodetail: cause.messageviolations in GenericAcpAdapter.ts, while noting the AcpRegistryDriver pattern in the summary since it mirrors existing driver implementations. Now I need to check whetherTextGenerationError's detail field has similar issues.… I need to add line numbers for… I'm working through the code review now. For the GenericAcpSupport.ts file, I need to flag line 30 and reference line 58, then provide a fix for removing an interface field—since it spans multiple hunks, I'll describe the change rather than show a full diff. For the other comments around line 626, I can suggest inline fixes for the cause.message references since those are self-contained single-line changes.…
1. modify_pr — Reviewed the new Effect service code in this PR (AcpRegistryDriver.ts, AcpRegistryProvider.ts, GenericAcpAdapter.ts, GenericAcpSupport.ts, AcpRegistryCatalog.ts, contracts/settings/rpc additions). Module namespace imports, Effect.fn usage, layer/Effect.service acquisition in the driver and catalog, and Schema.TaggedErrorClass usage all look consistent with the conventions. Two convention issues, both in newly added server code: 1. GenericAcpSupport.makeGenericAcpRuntime takes a ChildProcessSpawner["Service"] instance and re-provides it through Layer.succeed, so an owned Effect dependency is injected as a value instead of being required from the environment. 2. GenericAcpAdapter wraps failures with detail: cause.message, which makes the wrapper's caller-visible message a restatement of the cause instead of stable structural context. AcpRegistryDriver.ts has the same cause-derived detail shape (detail: \Failed to build ACP Registry snapshot: ${cause.message ?? String(cause)}`); it mirrors the existing drivers, so it is noted here rather than commented inline, but new code should prefer a fixed structural detail with the failure preserved only as cause`.
Agent Credits: 152 credits