Skip to content

fix(orchestration): refresh GitHub waitpoint deadlines

MacroscopeApp / Macroscope - Effect Service Conventions failed Aug 3, 2026 in 3m 16s

Effect Service Conventions: 3 issues found

Three new Effect service modules own their construction but inline it into Layer.effect(...) without exporting a make, breaking the canonical module order (imports → errors/schemas → Context.Service tag → make → layer) used across apps/server/src.

  • apps/server/src/github/GitHubWaitpointService.ts — construction inlined in Layer.effect; no exported make.
  • apps/server/src/github/GitHubWaitpointStore.ts — construction inlined in Layer.effect; no exported make.
  • apps/server/src/github/GitHubPullRequestProbe.ts — construction inlined in Layer.effect; no exported make.

Three inline review comments were posted with the expected 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: the PR-authored commits on this branch (25ff158, 13537f5, 4609cd3) touching apps/server/src/github/*, apps/server/src/mcp/*, apps/server/src/persistence/*, apps/server/src/server.ts, and packages/contracts/src/*.

Previously flagged items are now resolved and were not re-reported:

  • GitHubWaitpointServiceShape / GitHubWaitpointStoreShape were removed and the interfaces are now inline in the Context.Service declarations, with GitHubWaitpointService["Service"]["get"] used for the inferred member type.
  • storeError now passes an existing GitHubWaitpointStoreError through instead of re-wrapping it.
  • The register:reload invariant no longer manufactures an Error to populate cause; cause is now Schema.optional(Schema.Defect()).

Conventions verified as satisfied in the changed scope: namespace imports from effect/* subpaths and local service modules (named imports retained only for @t3tools/contracts, standalone types, and the GitHubPullRequestSnapshot schema); structured Schema.TaggedErrorClass failures with messages derived from stable attributes and real underlying causes preserved; dependencies acquired from the environment via yield* Foo.Foo with no ManagedRuntime/runPromise in service code; Layer.succeed occurrences are confined to test seams; new backend behavior is covered by focused tests rather than tests added purely for a refactor.


🔍 Agent activity — 1 tool call

💭 I'm examining the PR diff to understand what changes were made. The merge base is creating a massive diff because the branch was built on another feature branch. Instead of trying to parse through 820 files, I should focus on the actual PR changes in the GitHub-related files and look at the author's recent commits on this branch—prior reviews flagged GitHubWaitpointStore.ts and GitHubWaitpointService.ts as areas of concern. Since the last review at commit 13537f5, the head has moved to 4609cd3, so I'll diff that range to see what's actually changed since then. Looks like the branch was force-pushed, so I need to find which feature files changed. The GitHub PR files API would return too many results, so instead I'll check what's in the head directory directly. I've identified the three commits from the PR author and need to examine the diff across that range to see what changes were introduced. I'm checking this error design against naming conventions—specifically whether GitHubWaitpointServiceError as a generic tag combined with a single-value operation discriminator creates redundant encoding of the same distinction. The rule suggests using either a specific error tag or a multi-value operation field, not both for the same distinction. Here the tag is generic but the operation only ever takes "claim:lease-token", which is borderline. I notice the store error mapper was already fixed in a prior run to use multi-value operations, and the register:reload error was corrected to make the cause optional—both good improvements that align with the convention. For GitHubWaitpointServiceError, renaming to something like GitHubWaitpointLeaseTokenError and dropping the operation field would be cleaner since there's only one usage. This is new code in the PR, so it's worth flagging as a moderate finding, though it's a marginal case. OrchestratorMcpServiceShape is pre-existing legacy code that the PR only adds methods to, so I'll skip flagging that. The errorMessage function that persists arbitrary error strings to the database is borderline — it's stored as domain state rather than an error attribute, so I'll leave it. Now checking the import conventions... Schemas in helper modules are allowed, so the store's import is fine. Both services use Layer.effect() correctly, and while workerLive isn't a strict convention, the canonical layer export exists alongside it. Now checking whether both modules properly export a make function since they own their own construction. I should check what the repo's recent service modules actually do—whether they export make or inline construction in Layer.effect. If exporting make is the standard pattern, then these three modules are inconsistent; if inlining is common, it's fine. Also, I'm noticing process.cwd() being called directly inside the service, which violates the principle of acquiring dependencies through Effect services rather than relying on Node globals—there's probably a workspace or path service I should be using instead. GitHubWaitpointNotFoundError is a pure domain error with an optional thread ID and no cause needed. GitHubPullRequestProbeDecodeError has proper structural fields and a cause. For isGitHubWaitpointStoreError, it's a private Schema.is constant that violates the convention of exporting direct schema predicates — the rule flags private Schema.is constants only when they're wrapped by a redundant same-signature function, which isn't the case here since it's used directly. The storeError mapper performs legitimate pass-through classification of existing domain errors, which the conventions explicitly allow. The question is whether this detached production-side mapper should be converted to a static factory on the target error type instead, but the convention permits this classification pattern when it's reusable across multiple call sites. Now I'm checking the recent commits to see what's changed since my last review — looking at the diff between the two most recent commits to identify what's actually new. The author has addressed the earlier findings, so I'm now evaluating the remaining candidates. Looking at GitHubWaitpointServiceError with its single-value operation field—this seems like a borderline case where the convention isn't clearly violated. The rule allows either distinct error classes without redundant discriminators or a single service-level error with multiple operation values, but here we have one service-level error with just one operation value, which technically fits neither pattern perfectly. There's only one instance of this construction, so I'm uncertain whether to flag it as a clear violation. For the other candidates: I need to check the repo's pattern for exported make functions in service modules to decide if that's worth flagging, and regarding process.cwd() in the service—while it's an imperative API, it's being used in an Effect layer rather than in Layer.succeed, so it doesn't clearly match the listed violations about hiding dependencies. The real issue there might be correctness rather than a convention violation, and the repo likely has a dedicated WorkspacePaths or ProjectService for resolving the project directory anyway. I'm noticing a pattern violation across these three new modules—they're inlining the Effect construction directly into Layer.effect() instead of exporting a separate make function, which breaks the established convention. The codebase consistently uses export const make = Effect.gen(...) followed by export const layer = Layer.effect(Tag, make), and the style guide explicitly requires exporting a real make when the module owns construction. This is a legitimate inconsistency worth flagging. I'm deciding whether to post separate comments on each of the three new modules or consolidate them. Since each file has its own distinct violation of the make export convention, I'll post three brief one-sentence comments—one on GitHubWaitpointService.ts, one on GitHubWaitpointStore.ts, and one on GitHubPullRequestProbe.ts—to flag the pattern without being overly verbose. Let me verify the layer construction includes the necessary dependencies like workerLive and runtimeLayer that server.ts expects, and confirm the service definition follows the canonical order of imports, constants, interfaces, errors, tag, helpers, and layer. That checks out. I'm also noticing that GitHubWaitpointServiceError uses operation: Schema.String and deliverClaim stringifies errors into the lastError persisted field—I need to check whether that violates the rule about not copying arbitrary defect text into diagnostic fields. The convention suggests using Effect.catchTags to handle the GitHubWaitpointNotFoundError distinctly, then mapping any remaining errors to a generic orchestration error — this is more idiomatic than manually inspecting the _tag property with a string comparison. Wait, I'm realizing failure(...) returns an Effect that fails, so passing it as the error value inside Effect.mapError might be creating an Effect-wrapped error instead of a plain error object. I should check how the existing code handles this pattern for scheduled tasks to see if this is actually a bug or if there's something I'm missing about how the error handling works. The failure(...) function returns an OrchestratorMcpFailure value that works with Effect.mapError, and I can also use yield* on it since TaggedError instances are yieldable in Effect v4. This pattern aligns with the existing code style, and the _tag === comparison mirrors how isThreadManagementError is already being used elsewhere. Since this is a mapError to a value rather than a catchTags situation, this approach is fine. Now I'm flagging the missing make exports in the three new service modules—this is a clear convention violation since the repo consistently follows the export const make + export const layer = Layer.effect(Tag, make) pattern, and all three modules own their own construction. With these findings, the check should fail rather than return "All clear." For the GitHubWaitpointServiceError, I'm reconsidering whether to flag it. The error uses a generic tag with a Schema.String operation field (not a literal union), and it's constructed in only one place. The convention against single-value discriminators applies specifically to "specific error tag + single-value operation," but here the tag is generic and the field is unbounded, so it doesn't quite fit. I'll skip flagging this one to avoid being overly pedantic. Let me verify the store's register invariant failure follows the same pattern. Now I'm noticing that the store error lacks entity context—specifically the waitpoint ID—even though it's available in scope when the error is mapped. The convention calls for capturing stable domain context like resource or entity identifiers, but adding a waitpointId field would require changes across all the mappers. This is a moderate finding I've already hinted at, so I'm deciding whether it's worth flagging as a separate issue. Let me also verify the behavior of deliveryLeaseExpiresAt = nextPollAt on rescheduleClaim to make sure it's correct. For the missing exported make functions, I'm thinking about consolidating into one comment per file rather than three separate ones—that's cleaner. I'll target the specific lines in GitHubWaitpointService.ts, GitHubWaitpointStore.ts, and GitHubPullRequestProbe.ts where these are defined. Since these are new files in the diff, all lines are fair game for inline comments. Let me verify the exact line numbers against the head files. I'm posting a review with three comments and diff suggestions. For the GitHubWaitpointService file, I'll suggest refactoring to extract the Effect.gen logic into a separate make function, then wrap it with Layer.effect. Since there are multiple hunks involved, I'll skip the diff format and describe the changes in text instead.

1. modify_pr — Three new Effect service modules inline their construction directly into Layer.effect(...) instead of exporting a make. The convention for a module that owns construction is imports → errors/schemas → Context.Service tag → export const make → export const layer = Layer.effect(Tag, make), which is also what the rest of apps/server/src does (e.g. sourceControl/GitHubCli.ts, orchestration-v2/ThreadLifecycleService.ts, checkpointing/CheckpointStore.ts). Everything else in the waitpoint code looks consistent with the conventions: inline service interfaces, Foo["Service"] references, namespace imports from effect/* subpaths and local service modules, structured Schema.TaggedErrorClass failures with real causes, and store errors that pass an existing domain error through instead of re-wrapping it.


Agent Credits: 177 credits