Repository navigation
Effect Service Conventions: 4 issues found
Reviewed the Effect service code in this PR's changed scope, concentrating on the follow-up commits (92f8ba5e1..9393ecc32) that thread pullRequests.diffFileContents through the provider CLIs, provider port, service, RPC layer and client runtime.
4 concrete violations, all of the same convention (error modelling): a non-decode failure wrapped in the decode-oriented *ReadError with a manufactured new Error(...) as cause, which invents a cause where nothing failed and keeps the real context (file path, refusal reason) inside a string instead of structural attributes.
apps/server/src/pullRequest/GitHubPullRequestCli.ts:760-765— missing base/head revisions raised asGitHubPullRequestReadErrorwith a syntheticcause; should be its own tagged error with structural fields, like the siblingGitHubDiffCursorError/GitHubDiffCommitError.apps/server/src/pullRequest/GitHubPullRequestCli.ts:790-799— "exceeds the 1 MB expansion limit" and "is binary" distinguished only in a manufacturedErrormessage, with the file path stored in that string rather than as an attribute.apps/server/src/pullRequest/GitLabPullRequestCli.ts:712-718— commit with no usable parent raised asGitLabMergeRequestReadErrorwith a syntheticcause.apps/server/src/pullRequest/GitLabPullRequestCli.ts:872-883— same size-limit/binary pattern as the GitHub reader.
Inline comments with suggested fixes have been 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.
Scope
The PR targets main while stacked on #4849, so the aggregate diff carries that PR's ancestry. Review focused on the three follow-up commits under review (refactor(web): share diff rendering foundations, feat(pull-requests): expand omitted diff context, feat(web): keep pull requests open as panel tabs), with the rest of the changed scope scanned for the convention markers below.
Conventions checked and found clean
- Imports: no consolidated named imports from
"effect"; library modules imported as namespaces fromeffect/*subpaths; local service modules imported as namespaces (AzureDevOpsCli.AzureDevOpsCli,AzureDevOpsPullRequestCli.layer) with no aliasedlayer/makere-exports. - Service definition:
Context.Servicetags declare their interface inline,makeandlayerexported in canonical order, no residualFooShapetypes, member types referenced asFoo["Service"]["member"]. - Dependency acquisition: production constructors take dependencies from the environment (
yield* AzureDevOpsCli.AzureDevOpsCli,yield* GitHubPullRequestCli...); no service-instance parameters in production factories.Layer.succeedappears only in tests and in the application-owned WS layer wiring; noManagedRuntime.make,runPromiseorrunPromiseExitin domain services, repositories or the new web/pull-request modules. - Runtime boundaries: the new provider port member
getDiffFileContentsis optional and gated oncapabilities.diffinPullRequestService, with the unsupported case returning a typed failure rather than bypassing the layer. - Errors elsewhere:
Schema.TaggedErrorClasswith structured attributes;messagederived from attributes rather than fromcause; nodetailfield copyingcause.messagewas introduced by these commits; no error-construction helper wrappers or privateSchema.iswrappers added. - File layout/migrations: no compatibility re-export shims;
LocalCommentAnnotationwas moved (not duplicated) todiffs/DiffCommentAnnotation, with tests and every consumer updated, and the sharedStyledDiffCodeViewadapter replaces duplicatedCodeViewconfiguration with a lint rule enforcing it.
Note (not reported inline)
apps/server/src/pullRequest/GitHubPullRequestCli.ts:1052 uses Effect.catchTag("GitHubCliCommandError", ...) where the convention asks for Effect.catchTags({ ... }). It comes from the stacked base (#4849) rather than the commits under review here, so it is noted for that PR instead of being raised again on this one.
🔍 Agent activity — 1 tool call
💭 I'm working through the scope of what needs reviewing here — the PR includes changes from a few commits, but there's some ambiguity about whether to focus on the specific range the author mentioned or the full merge-base comparison, and I need to figure out which diff actually represents the changes to evaluate for Effect service conventions. Looking at the follow-up commits, I'm spotting violations in GitHubPullRequestCli.ts where custom errors are being constructed with
causefields that just wrap plain Error objects with embedded messages, which goes against the convention of not manufacturing Errors just to populate the cause field. I'm noticing thatGitHubPullRequestReadErroris being misused here — it's meant specifically for decode failures with a realSchema.Defect()cause, but the new code is repurposing it for three unrelated conditions (missing refs, oversized files, binary files) by artificially creatingErrorobjects as causes. This breaks both the documented constraint against manufacturing errors just to fill the cause field and the pattern the codebase already established elsewhere. The real issue is that the file path context is getting buried in a synthetic error message rather than being captured as a proper structural field on the error class itself, which means that useful domain information is only accessible through string parsing. I should verify the exact line numbers in the current code to add inline comments, then check whether GitLab has the same problem and whetherGitLabMergeRequestReadErroralso requires a cause. Now Looking at the diff for line 527 in getMergeRequestDiff, this truncation is actually pre-existing from the base PR #4849, not something new in the follow-up. Since the whole GitLabPullRequestCli.ts file is new in that base PR, the pattern already exists in the aggregate diff against main, which means it's technically part of the changed scope. The merge base for this check is main, so everything from #4849 is included in the comparison. For thecause: new Error(...)violations, these do seem like genuine convention breaches — the code is manufacturing errors to satisfy the requiredcausefield for decode-oriented error classes when there's no underlying failure (truncated output, binary content, missing refs). The convention explicitly says pure validation or domain errors created without an underlying failure shouldn't need a cause, and the path context is only embedded in the synthetic message rather than captured as stable, serializable domain context. Looking at the other findings: GitLab'sgetMergeRequestDiffFileContentsusesreturn yield* Effect.fail(new GitLabDiffCommitError(...))which is just a style choice, not a convention issue. ForSchema.Defect(), GitHubPullRequestReadError's tag and operation field identify the failure structurally and the message doesn't stringify the cause, so that's acceptable. ThePullRequestService.diffFileContentserror I'm noticing acatchTagusage at line 1052 in GitHubPullRequestCli.ts that violates the convention of usingcatchTagswith an object instead. I need to check if this line falls within the diff range by examining whether the file was modified relative to main. I'm looking at acatchTagusage that was introduced in PR #4849, which violates the project's convention of usingcatchTagswith an object pattern instead. Since this code change falls within the PR's diff relative to the merge base, it's a clear violation that should be flagged by the check. The entire pullRequest directory is new to this PR, so I should do a thorough review of the Effect conventions across these ~10 new server modules. I'll focus on checking the import patterns—confirming no named imports from "effect"—and validating the Context.Service structure. Since I'm spotting two issues in the GitHubPullRequestCli file where error handling is inconsistent. Lines 760-765 are creating a generic Error as a cause for non-decode failures, but this should be its own dedicated error type with proper fields like the other GitHub-related errors. The same problem appears at lines 790-799, where additional context like path and limit information should be part of the error structure rather than buried in a synthetic cause. Looking at the error handling in that file section, I'm seeing how a non-decode failure gets wrapped in the decode error structure with a manufactured cause field—the refs read actually succeeded but didn't return any usable SHAs, so the code synthesizes an Error just to meet the required cause property of the GitHubPullRequestReadError type. The message ends up as "returned an unreadable getPullRequestDiffFileContents response", and I notice the sibling error types above it (GitHubDiffCursorError, GitHubDiffCommitError, GitHubViewerLoginUnavailableError) follow a cleaner pattern with dedicated tagged errors that have structural fields and no synthetic cause. I'm thinking a similar dedicated error like GitHubDiffRevisionsUnavailableError with command, cwd, and number fields would be more consistent, though that would require adding a new class rather than just a diff. Moving to the readFile failure section, I'm seeing two distinct failure modes—exceeding the 1 MB expansion limit and binary files—encoded only in the text of a manufactured Error, with the file path buried in that string instead of being a structural attribute. A dedicated error like GitHubDiffFileUnavailableError with a reason field that's either "too-large" or "binary", plus a path property, would make this much clearer and let the message derive from those fields rather than being synthetic.
1. modify_pr — ## Effect service conventions Reviewed the Effect service code in this PR's changed scope, focused on the follow-up commits (92f8ba5e1..9393ecc32) that add the pullRequests.diffFileContents path through the provider CLIs, provider port, service, RPC and client runtime. Service shape is fine: Context.Service tags with inline interfaces, namespace imports from effect/* subpaths, make + layer in canonical order, Foo["Service"] member types, dependencies acquired with yield* Foo.Foo, no ManagedRuntime/runPromise outside application boundaries, and the optional getDiffFileContents port member is guarded by the provider capability rather than faked. Four findings, all the same convention: the new non-decode failures in getPullRequestDiffFileContents / getMergeRequestDiffFileContents are funnelled into the decode-oriented *ReadError with a manufactured new Error(...) as cause, which both invents a cause where no failure occurred and leaves the real context (file path, why the read is refused) only inside a string. Both files already model this class of outcome as its own tagged error (GitHubDiffCursorError, GitHubDiffCommitError, GitLabDiffCommitError, GitLabViewerUnavailableError), so the fix follows the sibling pattern.
Agent Credits: 401 credits