Repository navigation
Effect Service Conventions: 3 findings
Reviewed the new ClaudeSessionHistory service, its layer wiring in server.ts/ws.ts, and the touched ClaudeAdapter paths. The service follows most conventions (inline interface in the Context.Service declaration, Foo["Service"]["…"] method typings, exported make + layer, dependencies acquired via yield*, no ManagedRuntime/runPromise/service-instance injection). Three concrete findings:
apps/server/src/provider/Layers/ClaudeSessionHistory.ts— combined module (schemas/helpers + tag +make+layer) placed in the legacyLayers/half of theServices/+Layers/split; canonical location isapps/server/src/provider/ClaudeSessionHistory.ts, with the tag id updated fromt3/provider/Layers/ClaudeSessionHistory.apps/server/src/provider/Layers/ClaudeSessionHistory.ts:60-62— service boundary imported as a named import (import { ProviderService } from "../Services/ProviderService.ts") instead of a module namespace, unlike the adjacentProviderInstanceRegistry/ProviderSessionDirectoryimports; theProviderSessionDirectoryWriteErrortype is also re-imported from a path already imported as a namespace.- Missing focused tests for new backend behavior: transcript summarization/parsing, the session-id path-traversal guard, the
bindSessionLaunchOptionsresume-cursor merge, and the newshouldEnableRemoteControllaunch path inClaudeAdapter.ts. The only test change is aLayer.mockadded toserver.test.ts.
Two 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 and found compliant:
- Effect library modules imported as namespaces from subpaths (
effect/Effect,effect/Layer,effect/Context,effect/Schema, …) in all touched files; no consolidated"effect"named imports introduced. - Service interface declared inline in
Context.Service; noFooShapetype retained for the new service. Method implementations are typed viaClaudeSessionHistory["Service"]["list"]etc. - Canonical
make(real construction, dependencies viayield* Foo.Foo) andexport const layer = Layer.effect(...); requirements are visible in the layer type and explicitly satisfied inserver.tsviaLayer.provide(RuntimeCoreDependenciesLive). - No
ManagedRuntime.make,runPromise, orrunPromiseExitadded in domain/service code; the onlyLayer.mock/Layer.mergeAllusage is theserver.test.tstest seam. - No new error classes introduced; the existing
ProviderValidationError(Schema.TaggedErrorClasswithoperation/issue/optionalcause, message derived from attributes) is constructed at the failure boundary without a redundant factory helper. Effect.catch(() => Effect.void)onstopSessionintentionally handles the whole error channel;Effect.resultis used to make the bind failure non-fatal — both acceptable, nocatchTag/catchIf-with-schema-predicate misuse.process.envpassed explicitly intoresolveClaudeConfigDirPathmatches the existingClaudeSkills.tssignature/precedent, not a hidden global dependency.- Web-side changes (
ChatView.tsx,ChatComposer.tsx, stores) are React boundary code and introduce no Effect runtime in domain code.
🔍 Agent activity — 1 tool call
💭 I'm checking the new ClaudeSessionHistory.ts file for convention violations, particularly whether it should be hoisted out of the Layers directory based on the file organization rules. I'm also reviewing the import patterns — specifically whether services like ProviderService and ServerSettingsService should be imported as namespaces at service boundaries rather than as named imports, which appears to violate the established convention. The
ClaudeSessionHistoryaccess pattern using bracket notation is correct.Effect.catchis properly handling the error channel by intentionally converting it to void. TheEffect.resultcheck withbindResult._tag === "Failure"works fine.resolveClaudeConfigDirPathis a pure helper function so the named import is appropriate. However, there's a potential issue withprocess.envbeing used directly inside the service as a hidden dependency rather than being injected through a Config service. The error handling reusesProviderValidationErrorfrom the shared errors module, which includes operation and issue fields where issue interpolates invalid values. The repo follows a legacy pattern where services live in Services/ and layers in Layers/, but the new ClaudeSessionHistory.ts file combines both tag and layer logic in a single file under Layers/. The convention suggests hoisting combined service-layer modules to domain/Foo.ts instead, which creates a conflict since this is a new unified file rather than a refactoring of existing code. I should check if there's a pattern in the repo — other recently added combined services like ProcessResourceMonitor.ts and TraceDiagnostics.ts are at flat paths, not nested in Layers/. This suggests either a git migration moving Services+Layers to flat paths, or ClaudeSessionHistory.ts is genuinely misplaced. It's a soft finding since the guideline says not to flag separate implementation modules just for being in implementation-oriented directories, but this isn't a separate module — it's the canonical single-file service that should follow the established pattern. I need to check the ProviderValidationError definition and see what error conventions are already established in Errors.ts, plus understand how ProviderService is being used elsewhere to determine if it should be imported as a named import or namespace. I'm also noticing that bindSessionLaunchOptions declares failures for ProviderValidationError and ProviderSessionDirectoryWriteError, but I should verify whether getBinding's potential read errors are properly accounted for in those failure declarations. The legacy code uses standaloneFooShapeinterfaces while the new file correctly uses inline interfaces. I'm checking the error channel declarations—bindSessionLaunchOptionsdeclaresProviderValidationError | ProviderSessionDirectoryWriteError, andgetBindingfails withProviderSessionDirectoryReadError, which is part of theWriteErrorunion, so the types compile correctly. Now I'm thinking through the main convention questions, starting with how to handle named imports of services. The convention states that at a service boundary, I should import the local service module as a namespace and use its public module shape. This file importsProviderInstanceRegistryandProviderSessionDirectoryas namespaces, which matches the convention, butProviderServiceis imported as a named import from the same-style module—that's inconsistent. The exception allows named imports for pure helpers, errors, schemas, config values, or standalone types, and since theProviderServicemodule is only used for its tag, this might be a violation, though low severity. The same issue appears withServerSettingsService. I should also check whetherserverSettings.tsexports aServerSettingsServiceclass alongside many other things, which would make a namespace import awkward. Beyond that, the file locationprovider/Layers/ClaudeSessionHistory.tscontaining a tag, make, and layer seems to contradict the repo's split convention where tags go inServices/and implementations inLayers/. The instructions say combined modules should be hoisted todomain/Foo.ts, not placed inLayers/. The tag key"t3/provider/Layers/ClaudeSessionHistory"also differs from the pattern elsewhere where tags use"t3/provider/Services/...". I need to check if there are any precedents for combined modules in this repo to understand the canonical pattern. I see thatserverSettings.tsis a combined module where importingServerSettingsServiceas a named import is standard practice across the codebase, so that's fine. Now I'm checking howProviderServicegets imported elsewhere to see if there's a consistent pattern I should follow. I'm noticing theProviderServiceimport uses a named import pattern that diverges from the repo's convention of importing service modules as namespaces. While there's some precedent for this style elsewhere in the codebase, the guidelines specifically call for importing at service boundaries as a namespace to preserve the module's public shape, so this is worth flagging as a minor style inconsistency. Looking at theEffect.catchandEffect.resultusage, thesetThreadRemoteControlfunction is usingEffect.resultto handle both error types and return a unified response, which seems to be working around the catchTags convention rather than following it directly. Now I need to check whetherprocess.envis being used directly in the service — that would violate the dependency hiding convention. I should look at how other parts of the codebase like ClaudeProvider handle environment variable access to see if there's an established pattern. The environment variable usage follows the existing pattern in ClaudeSkills, so that's fine. TheEffect.provideServicecall for the Path is a bit unconventional but not problematic—the comment mentions it's required for Layer.provideMerge to work properly. For the ClaudeAdapter changes, I'm noticing it's usingEffect.promisewith manual error wrapping instead of the more idiomaticEffect.tryPromiseorEffect.resultapproach. Looking at the logging, the code passes the raw cause as a log annotation, which aligns with the convention of preserving the exact underlying error value as the cause field. Theserver.tslayer wiring uses the namespace pattern correctly withLayer.provideMergeandLayer.provide. Now I need to check whether there are any test implementations that need reviewing. This PR introduces a substantial new backend service with transcript parsing, path validation, and session binding logic, but there are no focused unit tests for the newClaudeSessionHistorybehavior — only a mock added toserver.test.ts. TheClaudeAdapterchange for remote control enabling also lacks test coverage inClaudeAdapter.test.ts. This violates the convention requiring focused tests when backend behavior changes, so that's a legitimate finding. The web-side changes inChatView.tsxdon't appear to useManagedRuntime.makeorrunPromisedirectly, justuseAtomCommandwith await, which is fine at the React boundary. I'm also noticing the service tag key uses"t3/provider/Layers/ClaudeSessionHistory"while all existing tags follow"t3/provider/Services/..."pattern. The canonical layout convention says to hoist combined domain services todomain/Foo.tswith a matching tag, not place them inLayers/. Since this is a new combined service, putting it inLayers/contradicts the expected structure. I'm settling on three findings: the file location issue, the named import ofProviderServicetag instead of using a namespace, and missing focused tests for the new backend behavior. Given these concrete violations, I'll mark this as a failure. The named import is actually defensible sinceProviderSessionReaper.tsfollows the same pattern, but the convention treats the instructions as authoritative when older code diverges, so I'll note it as a violation but frame it as a suggestion rather than a hard failure. Let me verify thelistfield is properly typed as anEffect.Effectwith no error channel and no requirements — the service methods check out there. Also,ClaudeSessionHistoryBindSessionLaunchOptionsInputis an exported interface that serves as an input type, not a service definition itself. I'm noticing a redundant import situation:ProviderSessionDirectoryis imported as a namespace on line 61, but then line 62 importsProviderSessionDirectoryWriteErrordirectly from the same module. Since the namespace import already exists, the type should be accessed through it instead. I'll combine this into a single inline comment covering both the import convention issue and this redundancy. For the location comment, I'll place it on line 28 where the@moduleJSDoc tag is. The missing test coverage should go in the review body rather than as an inline comment since there's no specific line to target. Let me verify thedecodeClaudeSettingsusage withEffect.orElseSucceed— that looks fine as is. Now I need to finalize the review and determine whether this should be marked as a failure based on the defaults and concurrent issues. Since there are findings and the check defaults to failure when issues are detected, I'll mark this as a failure. For the inline comments, I'm suggesting we consolidate the imports on lines 60-62 to use namespace imports consistently — converting the named import of the service tag to a namespace import, then updating the downstream references where those imports are used. For the tag ID on line 304, I should mention both the module location and the tag itself in a comment. Then I need to note that tests are missing and should be added to the body.
1. modify_pr — ## Effect Service Conventions Reviewed the new ClaudeSessionHistory service, its wiring in server.ts / ws.ts, and the touched ClaudeAdapter code paths. The service itself follows most conventions (inline interface in the Context.Service declaration, ClaudeSessionHistory["Service"]["…"] method typings, exported make + layer, dependencies acquired via yield* from the environment, no ManagedRuntime/runPromise in the service, no service-instance parameters). Three findings below. 1. apps/server/src/provider/Layers/ClaudeSessionHistory.ts is a single combined module (tag + make + layer) but lives in the Layers/ half of the legacy Services/ + Layers/ split, and its tag id encodes that path. Canonical location for a combined module is apps/server/src/provider/ClaudeSessionHistory.ts. 2. Service boundary imported with a named import instead of the module namespace (import { ProviderService } from "../Services/ProviderService.ts"), while the sibling boundaries in the same file use namespaces. 3. New backend behavior ships without focused tests. summarizeTranscript / parseTranscriptMessages / isValidClaudeSessionId (path-traversal guard) / the bindSessionLaunchOptions resume-cursor merge, plus the new shouldEnableRemoteControl launch path in ClaudeAdapter.ts, are all untested — the only test change is a Layer.mock added to server.test.ts so the existing suite keeps compiling. Per the change-discipline rule ("If backend behavior changes, require focused tests"), please add a focused ClaudeSessionHistory.test.ts (a FileSystem test layer over a fixture transcript directory is enough for list/getTranscript, and ProviderSessionDirectory assertions for the merge/validation paths) and extend ClaudeAdapter.test.ts for the remote-control enable path.
Agent Credits: 116 credits