Skip to content

test(issues): cover adapter source resolution

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

Effect Service Conventions: 1 issue found

1 finding

  • apps/server/src/sourceControl/githubGraphQlBudget.ts (L109-113): githubGraphQlBudget is a process-wide mutable singleton (host rate-limit snapshots + Date.now()) consumed directly inside GitHubIssueCli.make and GitHubPullRequestCli.make. That hides an implementation dependency in a module global instead of representing it in the Effect environment; it should be a Context.Service with make/layer, composed once in server.ts and provided to both PullRequestProviderRegistry.layer and IssueProviderRegistry.layer.

Previously flagged, now resolved

  • apps/server/src/issue/AzureDevOpsIssueProvider.ts: the provider now takes error.detail (a structural getter) rather than copying cause.message, so the caller-visible message no longer stacks.
  • apps/server/src/issue/AzureDevOpsIssueCli.ts: AzureDevOpsProjectUnknownError is now its own tagged error with no manufactured cause, alongside AzureDevOpsWorkItemIncompleteError.

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 in apps/server, packages/contracts, packages/client-runtime, and apps/web state:

  • apps/server/src/issue/*
    egistry, IssueProvider.ts, IssueProviderRegistry.ts, IssueService.ts, GitHubIssueCli.ts, GitLabIssueCli.ts, BitbucketIssueApi.ts, AzureDevOpsIssueCli.ts, and the four provider adapters
  • apps/server/src/sourceControl/githubGraphQlBudget.ts, gitHubReactionJson.ts, gitLabReactionJson.ts
  • apps/server/src/server.ts, ws.ts layer wiring
  • packages/contracts/src/issue.ts, rpc.ts, sourceControl.ts
  • packages/client-runtime/src/state/issues.ts, apps/web/src/state/issues.ts

Checks that passed

  • Subpath namespace imports (effect/Effect, effect/Layer, effect/Context, ...) throughout the new modules; no consolidated from "effect" imports in touched service code.
  • Canonical single-file order (imports, errors, Context.Service tag with inline interface, make, layer) in all new service modules; no standalone FooShape types; Foo["Service"] used for inferred interfaces in tests and helpers.
  • Errors use Schema.TaggedErrorClass with structured attributes, derive message from those attributes, keep the real failure in cause, and use a multi-value operation discriminator only where it varies.
  • Effect.catchTags used for known tagged failures; no catchTag, no catchIf with schema predicates, no private Schema.is wrappers.
  • No ManagedRuntime.make, runPromise, or runPromiseExit introduced in domain services; Layer.succeed occurrences are confined to tests and the app boundary in ws.ts/server.ts.
  • createIssueEnvironmentAtoms takes the existing shared connectionAtomRuntime rather than standing up a per-feature Atom runtime.
  • fromProviders(providers, sourceControlProviders) is a shared pure factory whose production caller (IssueProviderRegistry.make) still acquires SourceControlProviderRegistry with yield*, so the requirement remains visible in make's type; the explicit parameter is a legitimate test seam.

🔍 Agent activity — 2 tool calls

💭 I'm checking the diff between the previous commit and the current head to see what's changed. I'm looking at a large PR with 36k lines of changes. The previous review flagged two comments at commit fd22b14, but the head is now at af26eb1, so I need to check what's changed between those commits and whether those earlier issues were resolved. I need to get a file stat view of the diff to see what changed, since the force push means I can't do a traditional diff. Let me try requesting just the stats again—it seems the previous attempt returned the full diff even though I specified stat_only. I should try the git_diff tool again with the parameters ordered differently—maybe stat_only wasn't being recognized before. Let me simplify the call to just the essential parameters: base, head, stat_only, and the path filter. The diff is massive, so I need to focus on what matters most. Since the previous review already flagged two issues and the head has changed, I should first check if those findings still apply, then look through the new or modified service files for any convention violations. Checking whether the previous findings were addressed — the first issue about copying cause.message in AzureDevOpsIssueProvider.ts and the second about manufacturing an Error in AzureDevOpsIssueCli.ts. Looking at the updated code, I can see that AzureDevOpsProjectUnknownError no longer includes a cause property, which resolves the second finding, and the test is asserting the expected error message format. I need to check the rest of the PR for convention violations across the new service modules like IssueProvider, IssueProviderRegistry, IssueService, and the various CLI implementations, plus the state management code. I'm noticing that the detail field shouldn't copy from error.detail directly based on the instructions. Looking at IssueProviderError, it has both a reason discriminator with values like "missing-tool", "unauthenticated", and "failed" for control flow, and a detail field. Let me trace through IssueProvider.ts to see how these are being used. I'm checking IssueProviderRegistry.ts and IssueService.ts to understand the service conventions they follow—looking at how Context.Service, make, layer, dependency acquisition, ManagedRuntime, and Layer.succeed are used. Then I'll search through the changed files for any suspicious patterns. I'm thinking through whether fromProviders accepting a service instance parameter fits the pattern of flag factories or constructors that take implementation dependencies owned by the service. The make function pulls from the environment and passes it to this pure helper that's exported for testing, and since make itself yields the registry requirement, that dependency is exposed at the production level. I should check how PullRequestProviderRegistry.ts handles this pattern to see if it also accepts a service instance. The Issue variant adds an optional sourceControlProviders parameter to fromProviders, which production code yields from the environment and passes in. This is technically a test seam, but it violates the rule about flagging factories that accept service implementations as dependencies—the requirement shows up in the make function's type signature since that's where production acquires it. The optionality means the helper silently degrades when absent, though production always provides it. Since the factory is shared between tests and production, and production still declares the requirement explicitly, I'd consider this acceptable and wouldn't flag it. Now I'm examining the IssueService file itself—it's quite large at 66KB, so I need to look at its overall structure: the imports, error definitions, Context.Service definition, the make function, and the layer export. Looking at how toIssueError maps errors and constructs IssueOperationError with the detail field — I'm checking whether providers are consistently pulling detail from the error's structural getter versus deriving it from the cause message. The naming convention aligns with what the pull request providers already use, so that's consistent. Now I need to review the other new issue service modules—GitHubIssueCli.ts, GitLabIssueCli.ts, and BitbucketIssueApi.ts—to verify they're handling errors correctly, using catchIf and catchTag appropriately, leveraging Schema.is helpers, and following the right service definition order. Let me search through the issue directory and these new files to check how catchTag, catchIf, and Schema.is are being used. I'm scanning the PR for other TypeScript files outside the issue directory, particularly checking client-runtime and web components to ensure no per-feature managed or Atom runtimes are being created to reuse owned resources across multiple consumers. The issues.ts file properly reuses the existing connection runtime when creating issue environment atoms, which follows the guidelines. Now I need to get a complete list of changed files — trying different approaches like the GitHub API with pagination, git diff with stat output, or code research queries to efficiently identify all modified files without pulling massive patch data.

1. github_api_read_only — In repo pingdotgg/t3code, list every file path changed (added/modified/deleted) between merge base fee10de and af26eb1. Use git diff --name-status fee10def1afb63cecd9a626aeba2bb063828cf22 af26eb134a7a52339483761ea5fb2c0dd1a270bf. Output just the name-status list, no diffs.

💭 I'm looking at the Effect service files to understand the structure and identify any issues, starting with the GitHub, GitLab, and Bitbucket issue CLI implementations, along with the client-side state management for issues and the GitHub GraphQL budget tracking. The GitHubIssueReadError looks solid with its multi-value operation field and cause property. Now I'm checking githubGraphQlBudget.ts — it's imported as a named export, which works fine whether it's a service or a pure helper function. Let me take a quick look at that file to understand what it does. I'm spotting a convention violation here: githubGraphQlBudget is a module-level mutable singleton managing rate-limit state that gets consumed by Effect services, when it should be properly defined as an Effect service in the environment instead. This breaks the principle of not hiding dependencies in module globals or closures, and it's also relying on Date.now() for timing logic. The comment acknowledges the intent—one budget per server process shared across both registries—but the solution violates the pattern. The right approach would be to compose this resource once in an application-owned layer and pass its context to both registries rather than using a global singleton. Let me trace how GitHubIssueCli and GitHubPullRequestCli actually consume this. I'm spotting a design violation here—there's a module-level mutable singleton that's being shared as a hidden dependency between two Effect services, which breaks the principle of explicit dependency injection. The right approach is to compose this resource once at the application layer and pass it through properly rather than smuggling it in through closures or globals. There's also a Date.now() call with diagnostics disabled where a Clock service would be the idiomatic Effect solution, but the core issue is the module-global pattern. I need to flag the singleton export in githubGraphQlBudget.ts and potentially call out the consumers in GitHubIssueCli.ts. Since this file was newly added in this PR and both affected files are within scope, this is a fresh violation to surface. Before posting, I should double-check the error definitions in contracts/issue.ts to confirm they follow the same discriminator pattern as the pull request errors, ensuring consistency across the codebase. Alright, so this follows the same pull-request pattern. Let me look at the issues file in the client runtime to check for any ManagedRuntime or Atom runtime problems. Now I'm checking the remaining server service modules like GitLabIssueCli.ts and BitbucketIssueApi.ts to ensure they follow the same conventions, plus reviewing the helper file and how the IssueService layer gets wired through server.ts and ws.ts. I should also scan for any suspicious patterns across the codebase. The production code looks clean aside from the module-global budget variable. I'm noticing the Layer.provide pattern at ws.ts:2399 mirrors the pull requests setup at the app boundary, which is fine. But the githubGraphQlBudget is bothering me—it holds mutable state and gets consumed in Effect service constructors, which directly violates the convention against hiding dependencies in module globals. This is a hidden dependency that should be explicitly represented instead. I'm thinking the right fix is to wrap it as a Context.Service (like GitHubGraphQlBudget) with a layer composed once at the app level and provided to both registries. That way both the pull request and issue registries can depend on it explicitly rather than reaching into a closure. I need to check how the layers are currently wired in ws.ts to make a concrete suggestion about where to hoist this. Both services are wired up in server.ts, which is where I could provide a shared GitHubGraphQlBudget.layer. Now I need to identify the key findings, so let me check BitbucketIssueApi.ts and GitLabIssueCli.ts for similar patterns — specifically looking for error definitions that copy cause.message into detail, manufacture Errors from causes, or use redundant single-value discriminators. The BitbucketIssueViewerUnavailableError with empty fields checks out fine. Now I'm moving on to review the error handling in GitLabIssueCli.ts. Checking that the fail helpers in providers do real work like normalizing the reasonFor classification, so they're not just trivial wrappers—the AzureDevOps implementation confirms this pattern is being followed correctly. Now I'm verifying the GitHub, GitLab, and Bitbucket provider reasonFor functions to make sure they're not wrapping Schema.is in unnecessary function layers, and I've already confirmed there's no Schema.is usage there. Next I should check the React components in apps/web for proper use of ManagedRuntime and runPromise at the boundaries in the new issue-related code. The key issue here is that githubGraphQlBudget is a process-global singleton that's being used as a hidden dependency by two Effect services — both the newly added GitHubIssueCli and the modified GitHubPullRequestCli. Since the budget module itself is new in this PR, I need to determine whether this pattern violates the dependency convention rules. Looking at the doc comment, it explicitly justifies having a single shared process-wide instance for multiple provider layers — which is exactly the scenario the conventions address by requiring an application-owned layer instead. This feels like a legitimate violation worth flagging. The best place to post is on the singleton export around line 113 of githubGraphQlBudget.ts, since the entire file is new and all lines are in the diff. I'll add one inline comment there rather than duplicating feedback across multiple files. Now I'm drafting the comment itself, explaining how the mutable singleton creates hidden dependencies that both CLI constructors reach for directly, and suggesting they convert it to a proper Effect service with a single composed instance in server.ts. I'll mark this as a failure since it's a clear convention violation, then post the review on line 113.

2. modify_pr — One finding: the new GitHub GraphQL rate-limit budget is a process-wide mutable singleton that two Effect services depend on without declaring it in their environment. The Azure DevOps error findings from the previous run (stacked detail/message in AzureDevOpsIssueProvider, manufactured Error for cause in AzureDevOpsIssueCli) are both resolved at this head.


Agent Credits: 660 credits