Skip to content

fix(web): keep loaded review comments

MacroscopeApp / Macroscope - Effect Service Conventions failed Aug 15, 2026 in 2m 58s

Effect Service Conventions: 1 issue found

  • apps/server/src/pullRequest/PullRequestService.ts:389 — the new withRateLimitBackoff wrapper sets detail: error.message on PullRequestProviderError, 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 curated error.detail; suggest adding a detail getter to SourceControlRateLimitPausedError and 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/contracts and the pure retryAtFromHeader helper.
  • Both new services use the canonical order (imports, errors, Context.Service tag with inline interface, make, layer) and refer to their interfaces via Service["Service"].
  • GitHubGraphQlBudget and SourceControlRateLimit state lives in Refs created inside make, not module globals; time comes from Clock.currentTimeMillis. Layers are wired at PullRequestProviderRegistry.layer and server.ts, and both make/layer expose their requirements.
  • No ManagedRuntime.make, runPromise, or runPromiseExit introduced in server code; withRateLimitBackoff receives an instance already acquired from the environment in make, so it is a local decorator rather than fake injection.
  • New failures (SourceControlRateLimitPausedError, GitHubGraphQlBudgetPausedError, GitLabCliRateLimitError, AzureDevOpsCliRateLimitError) are Schema.TaggedErrorClass declarations 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 snapshotFrom uses JSON.parse, but the repo has a preferSchemaOverJson lint 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 how gitHubProviderFailure uses DateTime and Option imports. I'm noticing a conflict with the error handling convention — adding a detail field that just copies cause.message violates the guideline against redundant detail fields. But PullRequestProviderError already requires a detail field 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 copying error.message into a detail field 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 using error.detail where CLI errors expose a curated detail getter that's safe and structured, but this implementation is pulling from error.message on SourceControlRate instead. The LimitPausedError doesn't have a detail getter, 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 a detail getter to SourceControlRateLimitPausedError to 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 whether PullRequestService.layer needs SourceControlRateLimit provided 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 how withRateLimitBackoff should handle the SourceControlRateLimit.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 the detail: error.message issue, the convention explicitly forbids copying cause.message into a detail field and then using it in the wrapper message, which is exactly what's happening here. I need to either give SourceControlRateLimitPausedError a detail getter 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 like detail: "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 the detail: error.message approach and whether SourceControlRateLimitPausedError is properly exposed with its retryAt field flowing through to PullRequestProviderError. There's also a naming inconsistency with the githubGraphQlBudget.ts file 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 how PullRequestService's threadComments and PullRequestProviderApi.getReviewThreadComments handle their optional and Effect wrapper types. I also need to check the web implementation in PullRequestCodeTab.tsx which uses useAtomCommand instead of the Effect service, and then look at PullRequestDetailPanel.tsx to see how useLiveRefresh is being used with detailQuery.refresh and whether refreshDetail has been removed. I'm reconsidering how to handle the detail: error.message mapping and checking if there are similar patterns elsewhere. In protect, PullRequestProviderError is 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—GitHubGraphQlBudgetPausedError seems to have this issue with its host and response fields. Looking at the error handling conventions, withRateLimitBackoff uses tapError to check the reason field 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 a reason discriminator drives control flow or user-facing messages. PullRequestProviderError.reason is already doing that work—it controls behavior in isProviderUnusable and 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, and PullRequestProviderError is the intentional common shape that every provider reports through. The real issue is detail: error.message. The rule explicitly prohibits copying cause.message into a detail field 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 a detail getter to SourceControlRateLimitPausedError and 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 a detail getter to the error class that returns a curated string, then use detail: error.detail in the mapping. That's consistent with how all the other provider errors are handled. The violation is on line 389 of PullRequestService.ts where detail: error.message appears. 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 that retryAtFromHeader is properly imported from SourceControlRateLimit.ts into BitbucketApi and that the BitbucketPullRequestProvider doesn't copy error messages anywhere. The named import for retryAtFromHeader is 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 the error.message copy 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 uses detail: error.detail as a getter, but line 389 stands out by using error.message instead. The fix is to add a detail getter 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 copy cause.message into detail. 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 that detail is copying cause.message when 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