Repository navigation
Effect Service Conventions: 4 issues found
Four convention violations were found in the new scheduled-tasks service code and reported as inline review comments.
apps/server/src/orchestration/Services/TaskScheduler.ts— new service defined with a standaloneTaskSchedulerShapeand split acrossServices/+Layers/, withmakeTaskSchedulerLive/TaskSchedulerLiveinstead of canonicalmake/layer.apps/server/src/orchestration/Services/TaskFireReactor.ts— same split-module/*Shape/*Livepattern for a newly introduced service.apps/server/src/persistence/Services/ProjectionTasks.ts— new repository service usesProjectionTaskRepositoryShapeplusServices/+Layers/split andProjectionTaskRepositoryLive, diverging from the already-migrated persistence modules (AuthSessions.ts,ProviderSessionRuntime.ts).apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts—decodeTaskRowsbuildsPersistenceDecodeErrorwithissuecopied from the message of anErrormanufactured inparseScheduleJson, so the wrapper message is derived fromcause.messagerather than structural attributes.
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: reviewed every changed .ts/.tsx file in the include scope, focusing on newly created or consumed Effect services, and compared them against the canonical form already present in the repository.
Evidence that the canonical form is established here (so these are not legacy-only patterns):
apps/server/src/persistence/AuthSessions.ts,AuthPairingLinks.ts,ProviderSessionRuntime.ts— single module, interface inline inContext.Service,make,export const layer = Layer.effect(...).apps/server/src/persistence/Layers/ProviderSessionRuntime.tsis explicitly marked as a deprecated compatibility alias for the migrated module.- ~150 modules across
apps/,packages/, andinfra/already exportmake/layerfrom a single service module.
Checked and found acceptable:
- Dependency acquisition in
Layers/TaskScheduler.tsandLayers/TaskFireReactor.tsusesyield* Foofrom the environment;makeTaskSchedulerLive({ tickIntervalMs })injects pure configuration, not services. Layer.succeedoccurrences in tests and integration harnesses (OrchestrationEngineHarness,TaskScheduler.test.ts,server.test.ts, etc.) are legitimate test seams.- No
ManagedRuntime.make,runPromise, orrunPromiseExitintroduced in domain/service code. - Effect module imports use subpath namespaces;
@t3tools/contractsnamed imports are within convention. OrchestrationListTasksErroris aSchema.TaggedErrorClasswith structured fields and a preservedcause.
🔍 Agent activity — 15 tool calls
💭 I'm checking the new service files, TaskFireReactor and TaskScheduler, against convention — they define standalone shape interfaces and split definitions across Services/ and Layers/ directories, when the convention wants inline interfaces combined into a single domain file. But the existing repo actually follows the Services/Layers split pattern already for things like ProjectionSnapshotQuery and ThreadDeletionReactor. Since the instructions say to treat the newer convention as authoritative for any PR creating or refactoring Effect services, and this PR creates TaskFireReactor, TaskScheduler, and ProjectionTaskRepository with FooShape interfaces and Live-suffixed layers, it seems right to flag these as violations despite matching old patterns elsewhere in the codebase. I want to check whether other minor things stand out too — the scheduler's layer construction accepting config options looks fine, the test using a stubbed layer for ProjectionSnapshotQuery is an acceptable seam, and the imports across these new files look properly scoped. One thing worth a closer look is how parseScheduleJson throws a plain Error inside an Effect.try that gets wrapped into a PersistenceDecodeError — I want to see if that's treated as a legitimate cause-population pattern or something to flag. I'm noticing that the wrapper copies
cause.messagedirectly into theissuefield rather than deriving something structured, which brushes against the "don't just copy cause.message into a new field" guidance. There's also a rawthrow new Error(...)for a missing task row that becomes a defect inside an Effect.gen, which I should compare against existing conventions elsewhere in the codebase — let me check howPersistenceDecodeErrorand itstoPersistenceDecodeErrorhelper are typically constructed to judge whether this is consistent or a deviation.
1. browse_code — apps/server/src/orchestration/Services/*.ts
2. browse_code — apps/server/src/orchestration/*.ts
💭 Since the repo mostly follows the Services/Layers split, I'm checking whether there's already a canonical single-file pattern with an inline interface and
layerexport by searching forexport const layer = Layerusages.
3. git_tools — export const layer = Layer
4. git_tools — Shape>()(
💭 I'm noticing this repo is mid-migration, with the canonical single-file convention now dominant while older
Services/+Layers/patterns still linger in orchestration and persistence directories. The PR at hand adds new services that need converting to the canonical style, starting withTaskFireReactorwhich should be flattened into a single file with an inline interface,make, andlayer. I'm also flaggingTaskSchedulerandProjectionTasksas needing similar conversion, and noting that persistence files likeAuthSessions.tsandProviderSessionRuntime.tsalready follow the single-file convention -- confirming that new split-file services should be flagged. I want to verify the exact persistence directory structure and check that the naming pattern likeTaskFireReactorShape["Service"]matches convention. I'm also checking whether the newPersistenceDecodeErrorusage withissue: cause.messagematches an existing pattern, and looking at howparseScheduleJsonthrows errors with acausefield to see if that aligns with how errors are handled elsewhere in the codebase. I'm now examining whether creating a plain Error just to carry acauseand then mapping it into aPersistenceDecodeErrorwithissue: cause.messageis really adding value, or if it's just duplicating the message field unnecessarily -- it's a bit of a borderline case. I also noticeSchema.decodeUnknownSyncbeing used synchronously inside a service, which seems to go against typical Effect patterns favoringSchema.decodeUnknownEffect. I want to look at the persistence Errors module and existing decode helpers to confirm expected conventions, while setting aside the new React component inapps/websince it's not a service.
5. browse_code — apps/server/src/persistence/Errors.ts
6. browse_code — apps/server/src/persistence/**/*.ts
💭 I notice that small file is probably just a re-export shim, which isn't really my concern here. But the bigger issue is that the new services in this PR follow the legacy split-file pattern rather than the canonical single-file style with inline interface, make, and layer—which does seem like a real violation given the instructions apply whenever a PR creates or refactors an Effect service. Since this is a feature PR, not a cleanup PR, I want to be careful only to flag the newly created services rather than demand repo-wide changes. Let me actually look at the new service files themselves so I can write precise, targeted comments.
7. browse_code — apps/server/src/orchestration/Services/TaskScheduler.ts
8. browse_code — apps/server/src/orchestration/Services/TaskFireReactor.ts
9. browse_code — apps/server/src/orchestration/Services/ThreadDeletionReactor.ts
💭 Since TaskFireReactor and TaskScheduler mirror ThreadDeletionReactor exactly, these are new services following the legacy split-file Shape convention, so flagging them is legitimate rather than unrelated cleanup. I want to check the corresponding Layers files for these two to see if they use "Live" naming versus "layer" naming. I also notice
makeTaskSchedulerLiveisn't exported as a plainmakewith a separatelayerexport, which fits the same naming-convention finding. And in ProjectionSnapshotQuery's error handling, theissuefield just copiescause.message, which violates the convention against duplicating error details that get reused to build the wrapper message. I'm weighing whetherparseScheduleJsonmanufacturing a synthetic Error to wrap a schema parse failure is itself a violation — it likely should constructPersistenceDecodeErrordirectly from the SchemaError rather than throwing and catching an intermediate Error just to harvest its message. This seems like a legitimate, moderately strong finding since it's new code in a touched file, though the correlation type may need a taskId field added to fully support it. ThegetCommandReadModelthrowing on corrupt JSON also seems like a deliberate loud-failure choice given corrupted schedule data represents a database integrity issue. I'm also checking thecatchCauseusages in TaskScheduler and TaskFireReactor — these seem intentional since they handle the whole error channel including defects, so I won't flag those, and I'm scanning the web file for other issues like the random ID generation not going through an Effect service. Continuing through the other files, I see the RpcClientError import and Crypto import both look fine, and I'm now checking the forkParked import in TaskScheduler.ts. The remaining pattern is the same TaskSchedulerShape["tick"] usage that should be TaskScheduler["Service"]["tick"], part of the same finding. I want to compare ProjectionTaskRepository's service definition against an already-migrated module like AuthSessions.ts to see the canonical form for these interfaces.
10. browse_code — apps/server/src/persistence/AuthSessions.ts
11. browse_code — apps/server/src/persistence/Layers/ProviderSessionRuntime.ts
💭 I've confirmed the canonical convention already exists in the repo, seen clearly in AuthSessions.ts with its single-file, inline-interface, make/layer pattern—while the new task services deviate from this and follow a deprecated structure instead. Now I'm working through the specific findings to flag, starting with ProjectionTasks.ts, which splits into standalone shape/service/layer files rather than following the established convention. I identify TaskScheduler.ts and TaskFireReactor.ts as having the same structural deviation, then turn to a decoding issue in ProjectionSnapshotQuery.ts where errors are manufactured artificially just to carry context, when a cleaner approach would use schema-based decoding so the error structure derives naturally from the actual SchemaError as cause. I'm also weighing whether other areas like TaskSchedulerShape's tick method or the Layer.succeed test usages warrant flagging, concluding those look fine, then start considering another file in the orchestration layers. I'm now planning the four inline comments I'll post, working out the correct line ranges for each new file so the comments align properly on the diff.
12. browse_code — apps/server/src/persistence/Services/ProjectionTasks.ts
13. browse_code — apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts
💭 Now I need to locate the line numbers for decodeTaskRows in ProjectionSnapshotQuery.ts at head.
14. git_tools — decodeTaskRows|parseScheduleJson
💭 Now I'm working out which line ranges to anchor my review comments on, since I need them to fall within the diff's added lines. For the ProjectionSnapshotQuery.ts changes, I'll target the added lines around the Effect.try block, roughly lines 2419-2431. I'm drafting four review comments: three flag new service files (TaskScheduler.ts, TaskFireReactor.ts, ProjectionTasks.ts) that split shape and layer definitions across separate Services/ and Layers/ files rather than following the codebase's convention of a single module with inline Context.Service interface, exported make, and exported layer -- pointing to persistence/AuthSessions.ts as the canonical pattern. The fourth comment addresses the decodeTaskRows logic in ProjectionSnapshotQuery. I'll keep these concise without inline diff suggestions since they involve cross-file restructuring, and mark the overall review status as requesting changes due to these convention violations.
15. modify_pr — Effect service conventions: 4 findings on the newly introduced task services. The three new services are defined in the legacy Services/ + Layers/ split with standalone *Shape interfaces and *Live layer names, instead of the canonical single-module form (inline interface in Context.Service, exported make, exported layer) already used by e.g. apps/server/src/persistence/AuthSessions.ts. One error-construction site copies cause.message into a structural field.
Agent Credits: 133 credits