Skip to content

fix(mcp): read preference policy from thread shell

MacroscopeApp / Macroscope - Effect Service Conventions failed Aug 30, 2026 in 2m 25s

Effect Service Conventions: 1 issue found

Findings (1 new)

  • apps/server/src/mcp/EnvironmentPreferencesMcpService.test.ts (lines 38-51) — the new test module imports two local service modules with renamed member aliases (layer as threadManagementLayer, layer as environmentMcpLayer, make as makeEnvironmentMcpService), erasing the module namespace at a service boundary. Convention is a namespace import (ThreadManagement.layer, EnvironmentMcp.make, EnvironmentMcp.layer); the sibling EnvironmentMcpService.test.ts already imports without aliasing.

Carried over from a prior run (not re-posted)

  • apps/server/src/orchestration-v2/KeyedSerialExecutor.ts — ThreadDispatchLockV2 is declared in the generic executor utility module and its layer is exported as threadDispatchLockLayer rather than a canonical export const layer in a module that owns the service. Unchanged at this head commit.

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.

Reviewed in scope

  • apps/server/src/mcp/EnvironmentMcpService.ts
  • apps/server/src/mcp/EnvironmentMcpService.test.ts
  • apps/server/src/mcp/EnvironmentPreferencesMcpService.test.ts
  • apps/server/src/mcp/toolkits/environment/{handlers,tools,registration.test}.ts
  • apps/server/src/mcp/toolkits/worktree/registration.test.ts
  • apps/server/src/orchestration-v2/{KeyedSerialExecutor,Orchestrator,runtimeLayer}.ts
  • apps/server/src/orchestration-v2/testkit/ProviderReplayHarness.ts
  • packages/contracts/src/environmentMcp.ts
  • packages/shared/src/t3McpToolPresentation.ts and its test

Checked and found acceptable

  • EnvironmentMcpService acquires ThreadManagementService and ThreadDispatchLockV2 from the environment with yield*; make is exported for real construction and layer = Layer.effect(...) remains canonical. No service-instance parameters, ManagedRuntime.make, or runPromise were introduced in production code.
  • Explicit Effect.provideService of service instances in EnvironmentPreferencesMcpService.test.ts and the instrumented KeyedSerialExecutor are legitimate test seams, not production injection.
  • threadDispatchLockLayer appearing both inside orchestratorProvided and in OrchestrationV2LayerLive is a single module-level layer reference, so Effect layer memoization yields one shared executor — consistent with the existing documented pattern for providerContinuationRequestsLayer.
  • The added thread_not_found / permission_denied codes extend the pre-existing EnvironmentMcpFailure code discriminator; this is an established service-level error shape in this file, not a newly introduced switch-in-message pattern.
  • The new tests accompany a real behavior change (a new mutating MCP tool), so they are not "tests added solely to prove a mechanical refactor".
  • Effect library modules are imported as subpath namespaces throughout the touched files; @t3tools/contracts named imports are correct per the convention.

🔍 Agent activity — 13 tool calls

💭 Checking whether the flagged threadDispatchLockLayer naming issue was addressed between the two runs—comparing cfc5143 against fef7ed3, the diff shows it's still unchanged in KeyedSerialExecutor.ts, so I shouldn't repost that comment. Now I'm scanning the rest of the files for other potential issues.

1. browse_code — apps/server/src/orchestration-v2/KeyedSerialExecutor.ts

2. browse_code — apps/server/src/mcp/EnvironmentMcpService.ts

💭 The threadDispatchLockLayer naming issue was already flagged in a prior run, so I'll skip re-raising it since the instructions say not to duplicate comments unless the code changed. I should check the diff between fef7ed3 and cfc5143 to see if anything new was introduced.

3. git_tools — fef7ed3..cfc5143

💭 Since the previous commit got force-pushed away, I should check the current state for any remaining or new violations. Looking at the test file, I notice it imports layer and make with aliases from EnvironmentMcpService, which erases the module namespace — something the convention flags, though this is happening in a test file rather than at a service boundary. I'm also seeing threadDispatchLockLayer and a named import of ThreadDispatchLockV2 from KeyedSerialExecutor.ts, which relates to the same underlying issue — the recommendation to move it into a dedicated module and consume it as a namespace at those call sites. I want to check whether ThreadManagementService is imported the same way elsewhere as a pre-existing pattern, and whether the new test file uses Layer.succeed or a ManagedRuntime setup. Now I'm looking at how EnvironmentMcpFailure extends its existing switch-based message getter with new codes like "thread_not_found" and "permission_denied" — this is a pre-existing pattern being extended rather than a new violation, so I'd rather not flag it as new. I'm also comparing the changed error messages, starting with how capability_denied's message text was altered. The convention about preserving exposed error messages during structural refactors doesn't quite apply here since this is a behavior extension for a new mutation tool, not a refactor, so broadening the message seems reasonable. Now I want to check how ThreadManagementService is imported elsewhere — whether it's used as a namespace import — and look more closely at registration.test.ts and handlers.ts to find the main thing worth flagging.

4. git_tools — ThreadManagementService.ts"

5. git_tools — KeyedSerialExecutor

💭 I'm weighing whether adding a service tag to KeyedSerialExecutor.ts fits the module's purpose, since it's a generic utility rather than a service module, and the canonical layer export convention would clash with its existing makeKeyedSerialExecutor export. I already flagged this exact issue in a prior run and the code didn't change, so I shouldn't repeat it. Now I'm scanning for genuinely new findings, starting with EnvironmentPreferencesMcpService.test.ts — a test file named after a service that doesn't seem to exist, which is a stretch of a violation but worth noting since tests should be co-located with their actual service module. I'm also noticing that this new test file imports layer and make from EnvironmentMcpService.ts under aliases like environmentMcpLayer, which is exactly the pattern I've been told to flag — aliasing that erases the module namespace. The ProviderReplayHarness.ts option for a dispatch lock layer looks fine as test harness code, and I'm deciding not to flag the elaborate race test since it seems justified by an actual behavior change rather than being a superfluous addition. The error message switch and requireCapability implementation both look like pre-existing patterns, so I'm moving on to check whether EnvironmentMcpService dependencies are properly declared in the tools registration file.

6. browse_code — apps/server/src/mcp/toolkits/environment/tools.ts

7. browse_code — apps/server/src/mcp/EnvironmentPreferencesMcpService.test.ts

💭 I'm noticing the new test file imports the module with a namespace alias instead of importing the specific exports, which obscures the module boundary the convention cares about—that's a clear violation worth flagging. I'm now weighing whether it's still worth calling out even though it's in a test file rather than production code. Since the convention doesn't carve out an exception for tests, I'll flag it. There's another similarly aliased import in the same file that violates the same rule, so I can fold both into a single comment rather than raising them separately. I also notice other files in the repo already use this same aliasing pattern, but since this is a newly introduced file, the instruction to treat the convention as authoritative for new code applies, so I'll still flag it—just at low severity since it's a well-established pattern elsewhere. I'm now looking at another test file that pipes a service constructor through a provided dependency to see if that raises any similar concerns. That pattern is fine since it's an explicit, scoped instance provided within the test. The handwritten service stub in the settings module test is also acceptable as a test layer. Turning to whether there's a genuine dependency-acquisition issue: the environment service now correctly depends on the thread management and lock services via generator yields. But looking at the orchestrator's layer signature, it now requires the lock service too, and I'm noticing runtimeLayer.ts appears to provide that lock layer twice—once inside the orchestrator-provided layer and again in the merged layer. If that's true, it would create two separate executor instances instead of one, undermining the intended single admission boundary. I need to look at runtimeLayer.ts directly to confirm.

8. browse_code — apps/server/src/orchestration-v2/runtimeLayer.ts

💭 Since threadDispatchLockLayer is a module-level const used as the same reference in both spots, Effect's layer memoization should treat it as shared within a single build, so it gets constructed only once across the whole graph. Turning to EnvironmentMcpService.ts, I notice line 21 imports ThreadDispatchLockV2 by name from a service-owning module rather than as a namespace, but that's already covered by an earlier comment recommending namespace consumption at those call sites, so I can skip it here. The new test file's aliased imports (layer as environmentMcpLayer, make as makeEnvironmentMcpService, layer as threadManagementLayer) are a distinct, genuine convention issue worth flagging separately since they're in their own module. I need to pin down exact line numbers for the diff comment—since it's a new file, all lines 30-51 are eligible. I'm also verifying the EnvironmentMcpService.test.ts mock layer usage looks fine, checking that EnvironmentMcpWritingInstructions in contracts uses the filter helper correctly, and confirming whether Schema is imported as a namespace from effect/Schema in environmentMcp.ts.

9. browse_code — packages/contracts/src/environmentMcp.ts

10. browse_code — packages/contracts/src/environmentMcp.ts

💭 I'm noting that loadCurrentCaller's error mapping drops the underlying cause when converting to EnvironmentMcpFailure, but since that failure type has no cause field and other similar helpers do the same thing, this looks like a pre-existing pattern rather than something new to flag. I'll set this aside and instead focus on flagging the alias-import issue in the new test file, since it echoes the same namespace-import concern already raised about KeyedSerialExecutor. Checking a few other spots — the export change to EnvironmentMcpService.ts, the harness parameter naming, and the empty mock setup — all look fine and canonical. I want to look over handlers.ts in full before deciding if anything else deserves a comment.

11. browse_code — apps/server/src/mcp/toolkits/environment/handlers.ts

💭 I'm weighing whether the alias imports in the new test file are worth flagging, but that seems more like a style preference than a real convention violation. Actually, there's a more genuine issue: the test file is named EnvironmentPreferencesMcpService.test.ts but it's actually testing EnvironmentMcpService, referencing a module name that doesn't exist. I'm also checking other candidates — the canonical single-file ordering convention applies to source files, not tests, and the existing test naming already matches an established file. None of these feel like solid violations, so I'll settle on flagging the alias imports as the one real issue to raise.

12. browse_code — apps/server/src/mcp/EnvironmentMcpService.test.ts

💭 I notice the existing test file doesn't use the alias while the new file does, so given mixed precedent I'll flag this with a soft suggestion rather than a hard requirement. I'm also puzzling over conflicting instructions about the check defaulting to failure only with findings versus stopping immediately when clear, which seems to clash with the expected JSON schema output. Since fixing the import alias would require touching multiple call sites throughout the file rather than a single self-contained hunk, I'll skip providing a diff and just describe the recommended change in the comment itself before posting it.

13. modify_pr — One convention finding on the new test module's service imports. The prior finding on threadDispatchLockLayer in KeyedSerialExecutor.ts still stands but is not re-posted.


Agent Credits: 95 credits

Token usage: 20 uncached input · 491,027 cache-read input · 66,881 cache-write input · 9,550 output
Agent Credits may also include non-token charges from external tools such as web research.