Repository navigation
Effect Service Conventions: 1 issue found
apps/server/src/pullRequest/PullRequestService.ts:389— the newwithRateLimitBackoffwrapper setsdetail: error.messageonPullRequestProviderError, so the wrapper's message (${provider} failed in ${operation}: ${detail}) is derived from the cause's message instead of its own structural attributes. Every other provider mapping in this area passes a curatederror.detail; suggest adding adetailgetter toSourceControlRateLimitPausedErrorand using it.
The two findings from prior runs are resolved: the GraphQL budget is now a proper Context.Service with make/layer and Effect-clock time, and the pause is modeled as a dedicated GitHubGraphQlBudgetPausedError with structured host/resetAt fields.
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/sourceControl/SourceControlRateLimit.ts, apps/server/src/sourceControl/githubGraphQlBudget.ts, apps/server/src/sourceControl/GitLabCli.ts, apps/server/src/sourceControl/AzureDevOpsCli.ts, apps/server/src/sourceControl/BitbucketApi.ts, apps/server/src/pullRequest/PullRequestService.ts, PullRequestProvider.ts, PullRequestProviderRegistry.ts, the four provider modules, GitHubPullRequestCli.ts, apps/server/src/server.ts, apps/server/src/ws.ts, plus the touched web and contracts files.
Checked and found conforming:
- Namespace subpath imports (
effect/Effect,effect/Layer,effect/Ref,effect/Clock); named imports retained only for@t3tools/contractsand the pureretryAtFromHeaderhelper. - Both new services use the canonical order (imports, errors,
Context.Servicetag with inline interface,make,layer) and refer to their interfaces viaService["Service"]. GitHubGraphQlBudgetandSourceControlRateLimitstate lives inRefs created insidemake, not module globals; time comes fromClock.currentTimeMillis. Layers are wired atPullRequestProviderRegistry.layerandserver.ts, and bothmake/layerexpose their requirements.- No
ManagedRuntime.make,runPromise, orrunPromiseExitintroduced in server code;withRateLimitBackoffreceives an instance already acquired from the environment inmake, so it is a local decorator rather than fake injection. - New failures (
SourceControlRateLimitPausedError,GitHubGraphQlBudgetPausedError,GitLabCliRateLimitError,AzureDevOpsCliRateLimitError) areSchema.TaggedErrorClassdeclarations with bounded, serializable attributes, messages derived from those attributes, and real causes preserved where an underlying failure exists. - Backend behavior changes are covered by focused tests (
SourceControlRateLimit.test.ts,githubGraphQlBudget.test.ts,PullRequestProviderRateLimit.test.ts, CLI and service test additions).
🔍 Agent activity — 1 tool call
💭 The previous runs caught two issues that look like they've been fixed in the current version—githubGraphQlBudget is now properly set up as a Context.Service with a layer, and GitHubGraphQlBudgetPausedError is defined as a Schema.TaggedErrorClass. Now I'm scanning the current code for any new violations, starting with the new files like SourceControlRateLimit.ts and githubGraphQlBudget.ts to check if they follow the naming and structural conventions. Looking at the file naming—githubGraphQlBudget.ts uses lowercase while SourceControlRateLimit.ts uses PascalCase, which is a minor inconsistency, though it's not explicitly in the rules. The imports look good across both files, and the service definition order follows the expected pattern: constants, interfaces, error classes, Context.Service, helpers, make, and layer. The error classes are properly structured with derived messages and no unnecessary causes. I'm now checking whether the error detail getters are avoiding redundant encoding of the same information across different error types. Now I'm checking whether GitHubPullRequestCli.layer properly receives the GitHubGraphQlBudget dependency through PullRequestProviderRegistry, and verifying that PullRequestService.layer is consistently provided with SourceControlRateLimit.layer across the codebase — particularly in tests. I should also review the actual source files rather than just the diff to catch any convention issues I might have missed. Looking at the file naming patterns in the sourceControl directory, I notice githubGraphQlBudget.ts uses lowercase while service modules typically follow PascalCase conventions, and helper modules like gitHubPullRequestJson.ts are lowercase—so there's an inconsistency here that the naming rules don't explicitly address. I'm noticing that githubGraphQlBudget.ts breaks the naming convention—all other service modules with Context.Service are PascalCase, but this one is lowercase. The instructions say not to fail on optional style preferences, though this does seem like a consistent repo convention worth mentioning as a minor deviation without blocking the check. Now let me dig into the actual files for substantive issues. I'm noticing that
snapshotFromusesJSON.parse, but the repo has apreferSchemaOverJsonlint rule that would typically flag this without a disable comment, so I'm wondering if this would actually pass linting. Looking at the codebase, other server code consistently decodes GitHub JSON through Schema decoders rather than hand-rolled parsing, which suggests there's a pattern here — but this specific convention check doesn't explicitly cover Schema usage, so I'll skip flagging it. Now I'm examining the error handling in PullRequestService and GitHubPullRequestProvider, checking against the convention about not adding helpers that just wrap error construction, and looking at howgitHubProviderFailureuses DateTime and Option imports. I'm noticing a conflict with the error handling convention — adding adetailfield that just copiescause.messageviolates the guideline against redundant detail fields. But PullRequestProviderError already requires adetailfield for UI display, so I need to check how that's actually defined and whether the message derivation creates this exact problem. I'm noticing a violation of the error handling convention here. The code is copyingerror.messageinto adetailfield and then using that detail to construct the wrapper message, which is exactly the pattern the convention warns against. The existing providers handle this differently by usingerror.detailwhere CLI errors expose a curated detail getter that's safe and structured, but this implementation is pulling fromerror.messageon SourceControlRate instead. TheLimitPausedErrordoesn't have adetailgetter, and while its message is derived from structured attributes rather than raw, it's still technically a violation. The resulting wrapper message like "github failed in listChangeRequests: github requests to github.com are temporarily paused..." is a bit redundant but readable. I could add adetailgetter toSourceControlRateLimitPausedErrorto align with how other providers handle this, though it's a minor issue since the check defaults to failure and only warns about concrete violations. Now I need to look at the GraphQL read operations and review thread comments in the GitHub implementation, verify the budget observation is working correctly, and check whetherPullRequestService.layerneedsSourceControlRateLimitprovided in other consumers like MCP or test harnesses. I'm verifying that GitHubSubjectScopeError already exists and can be used inline in getReviewThreadComments, then checking the ws.ts RpcAuthorization setup and the web-side Effect service usage. Looking at the pull requests state file, everything checks out, and now I'm considering howwithRateLimitBackoffshould handle theSourceControlRateLimit.SourceControlRateLimit["Service"]type according to the rule about flagging factories or constructors that accept service types. The dependency is properly acquired via yield* in make, and the helper is a local pure decorator within the same module, so the requirement is satisfied — it's not fake DI. The rule about production service construction acquiring dependencies from the environment is met here, and passing the already-acquired instance to the helper is acceptable style, so I won't flag it. For thedetail: error.messageissue, the convention explicitly forbids copyingcause.messageinto adetailfield and then using it in the wrapper message, which is exactly what's happening here. I need to either give SourceControlRateLimitPausedError adetailgetter like other CLI errors or construct the detail from structured attributes like provider, host, and retryAt instead of just copying the message. The intent of the rule is to avoid duplicating unstructured cause.message data, so a better fix would be something likedetail: "Rate limit reached; requests to this host are paused."derived from the key attributes at the failure boundary. This feels like it could be arguable as a nitpick, but the guidance says to report concrete violations, so I should check if there's anything more substantial to address. Let me look at the GitLabCli change to see if they moved the "not-found" case correctly and verify there's no fallthrough bug in the switch statement. The AzureDevOpsCli.ts file has a new error that needs checking against the existing patterns. I'm also concerned about thedetail: error.messageapproach and whetherSourceControlRateLimitPausedErroris properly exposed with its retryAt field flowing through to PullRequestProviderError. There's also a naming inconsistency with thegithubGraphQlBudget.tsfile that doesn't align with the service module conventions. Looking at the file naming convention, service modules should follow PascalCase like the rest of the codebase, but this one is in camelCase. I should check if there's a documented convention in the repo's AGENTS.md or style guide that specifies service file naming to determine if this is worth addressing or just a minor style preference. I'm skipping the file naming issue and moving on to verify howPullRequestService'sthreadCommentsandPullRequestProviderApi.getReviewThreadCommentshandle their optional and Effect wrapper types. I also need to check the web implementation inPullRequestCodeTab.tsxwhich usesuseAtomCommandinstead of the Effect service, and then look atPullRequestDetailPanel.tsxto see howuseLiveRefreshis being used withdetailQuery.refreshand whetherrefreshDetailhas been removed. I'm reconsidering how to handle thedetail: error.messagemapping and checking if there are similar patterns elsewhere. Inprotect,PullRequestProviderErroris being constructed directly, which is clean. I should also keep in mind the principle of not duplicating the same distinction through both a specific error tag and a single-value operation or reason literal—GitHubGraphQlBudgetPausedErrorseems to have this issue with its host and response fields. Looking at the error handling conventions,withRateLimitBackoffusestapErrorto check thereasonfield directly, which is fine for a single error class. But there's a tension here: the convention suggests splitting semantically distinct failures into separate error classes when areasondiscriminator drives control flow or user-facing messages.PullRequestProviderError.reasonis already doing that work—it controls behavior inisProviderUnusableand now in rate-limit handling. Adding "rate-limited" to the existing reason union technically extends a pre-existing violation of that principle. That said, the instructions are clear about not demanding unrelated repository-wide cleanup, andPullRequestProviderErroris the intentional common shape that every provider reports through. The real issue isdetail: error.message. The rule explicitly prohibits copyingcause.messageinto adetailfield and then deriving the wrapper's message from it. That's exactly what's happening here—the message is constructed from the detail, which came from the error's message. This is a concrete violation the PR introduces, and the fix would be to add adetailgetter toSourceControlRateLimitPausedErrorand use that instead. I could build the detail from structured attributes like host and retryAt directly, but the cleaner approach is to follow the repo's established pattern: add adetailgetter to the error class that returns a curated string, then usedetail: error.detailin the mapping. That's consistent with how all the other provider errors are handled. The violation is on line 389 of PullRequestService.ts wheredetail: error.messageappears. Now I'm checking the other provider implementations — Azure DevOps spreads the failure object before adding detail, Bitbucket's response error includes retryAt from headers, and both handle 429 status codes properly. I should verify thatretryAtFromHeaderis properly imported from SourceControlRateLimit.ts into BitbucketApi and that the BitbucketPullRequestProvider doesn't copy error messages anywhere. The named import forretryAtFromHeaderis fine since it's a pure helper function. I'm weighing whether this convention violation is significant enough to fail the check — it's explicitly listed in the guidelines, not just a style preference, so I'm leaning toward posting the comment and marking it as a failure. But I should verify that theerror.messagecopy is actually safe and check how other parts of PullRequestService handle similar error construction to avoid false positives. I'm noticing that the server-side pull request code consistently usesdetail: error.detailas a getter, but line 389 stands out by usingerror.messageinstead. The fix is to add adetailgetter to SourceControlRateLimitPausedError and standardize on that pattern. I'll leave an inline comment on that line with the suggestion, though the actual implementation will need to span both the error class definition and the service file. For the check state, this is a clear convention violation — the rule explicitly says not to copycause.messageintodetail. Even though it's a smaller issue than the prior findings, it's consistent with the guidance to fail when there are findings. I'll mark this as a failure and post the review comment now. The comment will explain thatdetailis copyingcause.messagewhen it should instead use a curated value derived from the error's structured properties. Since this is a cross-file change (adding a getter to the error class), I can't provide an inline suggestion for line 389 alone — that would require the getter to exist first. I'll describe the fix conceptually and maybe show what the getter would look like, keeping it concise.
1. modify_pr — One finding on error construction in the new rate-limit wrapper. Everything else in the new SourceControlRateLimit and GitHubGraphQlBudget services follows the service conventions (namespace subpath imports, Context.Service with an inline interface, real make, exported layer, dependencies acquired with yield*, structured Schema.TaggedErrorClass failures with messages derived from attributes).
Agent Credits: 136 credits