Repository navigation
Effect Service Conventions: 1 issue found
WorkflowInspectionError discards the underlying failure instead of preserving it as cause.
packages/contracts/src/workflow.ts— the newWorkflowInspectionError(fieldsoperation,reason,detail) has nocausefield. Itsread-failed/not-foundreasons wrap real filesystem/Node failures, so the immediate underlying error, chain, and stack are lost. The service error conventions require a wrapped real failure to preserve the immediate underlying error ascausealongside the structural fields (optional here, sinceinvalid-path/unsupportedare pure validation reasons with no underlying failure). This also diverges from the codebase's own read-from-disk pattern inassets.ts, which usescause: Schema.Defect().apps/server/src/workflow/WorkflowInspectionService.ts— every wrap site (resolveContained,readScript,readJournal,readAgentTranscript) bindscausein itscatch/mapErrorcallback but never forwards it into the constructedWorkflowInspectionError, so the platform error is dropped.
Inline comments posted with the suggested fix.
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.
Scope reviewed: new/changed Effect service code under the configured include patterns, focused on the new WorkflowInspectionService and its workflow.ts contracts, plus the stopTask additions to ProviderService/ClaudeAdapter and the new thread.task.stop command/event.
Checked and found compliant:
- Namespace imports for Effect modules (
Context,Effect,FileSystem,Layer) inWorkflowInspectionService.ts; named imports from@t3tools/contractsare permitted. Context.Servicetag with inline interface, realmake, andexport const layerorder inWorkflowInspectionService.ts.messagederived from structural attributes (operation+ staticdetail), not from a stringified cause;detailvalues are human-authored constants, not copies ofcause.message.reasonis used only for diagnostics/tests, not to select the user-facing message or drive caller control flow, so a single service-level error with a multi-value discriminator is acceptable and separate error classes are not required.provider.task.stop.failedhandling inProviderCommandReactor.tsreuses the establishedappendProviderFailureActivity+Cause.prettypattern already pervasive in that file (not a newly introduced deviation).- Existing
ProviderServiceShape/ClaudeAdapterShapereferences are pre-existing types, not introduced by this PR, so they are out of scope for cleanup.
Reference for the expected pattern: packages/contracts/src/assets.ts (AssetWorkspaceAssetInspectionError with cause: Schema.Defect() for wrapped failures, AssetWorkspaceAssetNotFoundError without a cause for pure not-found).
🔍 Agent activity — 1 tool call
💭 I'm reviewing the new Effect service files for convention violations, starting with the WorkflowInspectionService and its contracts. I need to check that Effect modules are imported as namespaces and verify the rest of the conventions are being followed. The imports look good with proper Effect namespaces and allowed package imports. The service definition uses Context.Service with an inline interface, which is correct, and the make function is a proper constructor with Layer.effect for the layer definition. Now I'm examining the error class structure to see if it violates the convention about splitting semantically distinct failures into separate error classes when using discriminators like reason or kind. Looking at the WorkflowInspectionError, the reason field drives the test assertions but doesn't actually branch the user-facing message or control flow in the application code—the message is constructed from operation and detail instead. So reason is purely diagnostic and acceptable as a field. The detail field isn't copying cause.message either; it's a human-authored constant string like "Path must be absolute." that becomes part of the message. The real question is whether the error handling properly preserves the underlying filesystem error as a cause when wrapping failures. In the read operations, filesystem errors are being mapped to WorkflowInspectionError with a reason like "not-found" or "read-failed", but the original cause is discarded entirely. The convention requires preserving the immediate underlying error as a cause field when wrapping real failures. Since some constructions are pure validation errors without an underlying failure, cause should be optional, but it needs to be populated in the read-failed and not-found cases. Currently there's no cause field at all, so the error chain is lost—this is a legitimate violation of the convention. The core issue is that
WorkflowInspectionErrordoesn't preserve the underlying filesystem error as acausefield when wrapping real read failures—this violates the explicit convention to maintain the immediate underlying error. Let me check how other modified services handle errors.ProviderServiceuses the existingtoValidationErrorhelper for its newstopTaskmethod, which follows established patterns. Now I'm looking atClaudeAdapterto see how it implementsstopTask. The convention is clear that when wrapping real failures, I need to preserve the underlying error ascauseto maintain the error chain. ThereadScript,readJournal,readAgentTranscript, andresolveContainedfunctions all wrap filesystem errors intoWorkflowInspectionErrorwithout capturing the cause, which breaks the error chain. I should flag this as a violation and suggest adding acausefield to the error class, then passing the underlying error when constructing these wrapped errors. I need to adjust how the repo parameter is being formatted. I'm checking how the codebase handles wrapped failures by looking at thecausefield pattern—whether it usesSchema.Defect()as required orSchema.optional(Schema.Defect())—and I want to examine similar file-read error implementations in project.ts and assets to see if they follow the same structure with both cause and reason fields. Looking at how the codebase structures its error classes, I can see the pattern: errors that represent actual failures wrap the underlying issue withcause: Schema.Defect(), while not-found errors stand alone without a cause since they're not wrapping another failure. Comparing this toWorkflowInspectionError, the critical issue is that it drops the underlying filesystem errors entirely instead of preserving them ascause. The convention is clear that when translating a real failure, the original error must be kept in the chain so the complete context remains available for debugging. Thecausefield should be optional since some error cases like invalid-path originate without an underlying failure, while read-failed and not-found wrap real failures—so I need to mark this as a genuine convention violation where the wrapping sites drop the cause entirely. Let me locate the exact lines in the error class definition and the wrapping sites to comment on the fix needed. I'm seeing that the error handling in those catch and mapError blocks captures the cause but never actually passes it into the WorkflowInspectionError constructor — the error class doesn't even have a cause field. This breaks the pattern established elsewhere in the codebase like assets.ts, which properly preserves the underlying error. Let me check the full error class definition to understand what fields it actually supports. I'm checking whether the ProviderCommandReactor's handling of provider task failures and theprovider.task.stop.failedevent respects the conventions around what can go into detail fields. The reactor is usingCause.pretty(cause)to format the cause into a detail field, but I need to verify this doesn't violate the rule against copying raw wire payloads, credentials, or other sensitive data into those fields. Looking at the existing pattern in the file, this mirrors howappendProviderFailureActivityis already being used for session-stop-requested handling, so it's not introducing a new convention deviation—it's consistent with the established approach for provider failure activities in this reactor. Since this is activity payload detail meant for UI display rather than an error attribute, and it follows the pre-existing pattern, I'll move on without flagging it. TheCause.pretty(cause)pattern indetailforappendProviderFailureActivityis consistent with how it's used elsewhere in the file, including the new task-stop handler, so that's not a violation worth flagging. The only clear issue I've introduced is the missingcauseparameter inWorkflowInspectionError. I've checked the other new modules—the atom factory and pure logic files follow conventions fine. Looking at whether the singleWorkflowInspectionErrorclass with areasondiscriminator violates the convention: thereasonfield isn't driving control flow in the caller or selecting user-facing messages (those come fromoperationanddetail), so it's diagnostic and acceptable. The real violation is the dropped cause, whichassets.tsdemonstrates correctly, so I'll report that and fail the check. I'm planning two focused inline comments: one on the error class definition suggesting an optionalcausefield, and another on the service wrap site showing where the cause gets discarded. Let me draft the suggestion for the error schema to include the cause parameter. For the GitHub review, I'll submit it with these inline comments targeting the specific lines in the workflow contracts and service files, keeping the body concise and noting that this pattern applies across all wrap sites.
1. modify_pr — One Effect service convention issue: the new WorkflowInspectionError drops the underlying failure instead of preserving it as cause. See inline comments.
💭 I'm preparing the final JSON output to report the convention violation as a failed check-run.
Agent Credits: 90 credits