Skip to content

fix(onboarding): make recent agent thread imports safe to retry

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

Effect Service Conventions: 1 issue found

apps/server/src/project/AgentSessionImporter.ts — Import-time validation failures reuse OrchestrationCommandInvariantError with a fabricated commandType (agentSessions.import is an RPC method, not an orchestration command) and push all real context into a free-form detail string, collapsing two semantically distinct failures (unresumable Claude session id, thread owned by another project) into one generic tag. Suggested fix: declare dedicated Schema.TaggedErrorClass errors with structured attributes (source/providerSessionId, threadId/projectId) and derive each message from them.

One inline review comment was posted.

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.

Checked areas that were clean:

  • AgentSessionImporter.ts acquires every dependency from the environment (yield* AgentSessionScanner.AgentSessionScanner, OrchestrationEngineService, ProjectionSnapshotQuery, ProviderSessionDirectory, Crypto.Crypto); no service-instance parameters, no ManagedRuntime.make, no runPromise in domain code. The earlier Effect.serviceOption optional-dependency pattern flagged on a prior run has been replaced with real environment acquisition, and ws.ts now provides the service at the RPC boundary.
  • Effect modules are imported as subpath namespaces (effect/Effect, effect/Option, effect/Schema, effect/Crypto); local service modules use namespace imports and their public Service/layer shape. Named imports from @t3tools/contracts and the pure findProjectByPath helper are within the allowed exceptions.
  • AgentSessionScanner.ts keeps the canonical order (schemas, Context.Service tag with inline interface, make, layer); the new recentThreads member is added inline rather than via a standalone shape type, and callers use AgentSessionScanner["Service"][...] types.
  • New contract errors (AgentSessionImportProjectNotFoundError) use Schema.TaggedErrorClass with a message derived from structured attributes; the AgentSessionScanError wrapper preserves the real cause.
  • Layer.mock / Service.of(...) / Effect.provideService occurrences in the new tests are legitimate test seams, not production dependency injection.
  • Behavior changes (history import decider path, revert retention, awareness relay suppression) come with focused tests, so no additional test coverage is required.

🔍 Agent activity — 7 tool calls

💭 I'm reviewing the new AgentSessionImporter.ts file for adherence to Effect service conventions—checking that catching the whole error channel with Effect.catch is justified, and looking at how it reuses OrchestrationCommandInvariantError to signal conditions where an import should be skipped. I want to check whether this is a valid reuse of a command error type or an error-modeling mismatch, since the "commandType" field passed in looks like an RPC method name rather than an actual command type. I'm also examining how AgentSessionScanError gets constructed with an "operation" field during a project scan failure, comparing it against the earlier flagged issue in ws.ts about a fabricated cause, and checking whether the message wording still makes sense in this new context. I'm also noticing there's a module-scoped mutable cache for candidates inside the service's Effect.gen closure with no invalidation logic, which could lead to stale results in recentThreads even though it's not strictly a hidden-dependency violation under the stated conventions — more of a correctness concern I'll note separately from the Effect service convention check. I should pull up AgentSessionScanner.ts and the contracts error definitions to verify these details directly.

1. git_tools — AgentSessionScanError

2. git_tools — class OrchestrationCommandInvariantError

💭 I'm checking AgentSessionImporter.ts, where it reuses OrchestrationCommandInvariantError with commandType set to "agentSessions.import" for skip conditions that aren't actually orchestration commands—this looks like a potential misuse of that error type, since it's meant for commands, not RPC methods. Meanwhile, the AgentSessionScanError mapper at the boundary preserves the cause properly, though its "read-projects" operation label is a bit mismatched for an import context—minor but acceptable given the error union structure. I also want to confirm the previous ws.ts serviceOption issue is truly resolved by looking at the current region where providerSessionDirectory is pulled from context, alongside the head of the importer file.

3. browse_code — apps/server/src/project/AgentSessionImporter.ts

4. browse_code — apps/server/src/ws.ts

💭 I'm wondering why the handler explicitly re-provides services via Effect.provideService when they're already available in the enclosing context — likely because the handler's own type signature requires R = never, so explicit provision is needed even though it seems redundant. I want to check whether other handlers in ws.ts follow the same pattern to confirm this is the file's convention.

5. git_tools — Effect.provideService

💭 This seems to be the only spot in ws.ts using provideService directly, so I'm checking how other handlers with dependencies are typically structured—they usually acquire services from the enclosing context via closures, similar to how importRecentAgentThreads does it. Providing services acquired from the enclosing layer is legitimate as long as the handler's requirement type is never, so this pattern is acceptable even if a bit verbose. Now I'm looking at the AgentSessionScanner.ts changes, where the service caches candidates in a closure and recentThreads relies on that cache, with providerInstanceIds arrays being mutated via push. This hidden mutable cache isn't really a smuggled dependency, but it could cause recentThreads to return stale results if the scan already ran and new sessions were created afterward—more of a behavioral correctness concern than a strict convention violation, so I'm weighing whether it's worth flagging given the review seems to expect concrete convention breaches. I'm also examining error handling in AgentSessionImporter, where OrchestrationCommandInvariantError gets reused for non-orchestration-command failures with a free-form detail string and a misleading commandType field. This collapses two semantically distinct failures—an unresumable Claude session versus a thread owned by another project—into one generic error, which conflicts with the convention of splitting semantically distinct failures into separate tagged error classes when the failure reason determines the user-facing message. Continuing through the rest of the checklist: the catch block also handles AgentSessionScanError types appropriately, log annotations properly preserve cause without leaking secrets, and the named imports throughout (threadReducer, projectImport.logic, test files) all fall under allowed patterns for whole-package or pure-helper imports. Test files using Layer.mock, Service.of, and Effect.provideService are legitimate test seams, and I'm now checking the AgentSessionImporter test's use of provideService for correctness. I'm now looking closer at the scanner service's cachedCandidates closure to determine whether it violates the rule against hiding dependencies in module globals or singleton closures — but since this cache is scoped to a per-instance service rather than a true module-level global, it seems more like a potential staleness bug than an actual convention violation, so I'd treat it as out of scope for this narrowly-defined check. Checking the indentation confirms cachedCandidates is scoped inside the make function, not a module-level global, so that's fine. I want to double check the interface signature for recentThreads to make sure it's returning the expected effect type with the proper error channel. Now I'm checking a couple of other error definitions—the AgentSessionImportProjectNotFoundError looks correctly built with a tagged schema and derived message, and the reuse of AgentSessionScanError with a "read-projects" operation tag in the importer's shell-reading path seems acceptable since that literal covers the case. So my main finding remains the potential misuse of OrchestrationCommandInvariantError, and I want to see if other non-orchestration code in the repo does something similar. I should search for other instances of new OrchestrationCommandInvariantError outside the orchestration module to see if there's already precedent for this pattern.

6. git_tools — new OrchestrationCommandInvariantError(

💭 AgentSessionImporter turns out to be the only real production usage, and it's misusing an RPC method name as a fake commandType with a free-form detail string covering two distinct validation failures. Given the convention of using Schema.TaggedErrorClass with structured attributes and stable domain context instead of interpolated strings, I should flag this with an inline comment and a diff suggestion splitting it into separate error classes. I check the other files quickly — the projection/decider changes and threadReducer are pure functions, so those are fine, and the imports in AgentSessionImporter and its tests all use standard effect module paths without anything unusual. I confirm the dependency acquisition pattern there is correct since it's a use-case function rather than a service module, so no issue there. Since fixing the error class problem requires adding new classes across files rather than a single-hunk change, I decide not to include a diff and instead post it as an inline comment describing the needed refactor. These errors are caught and logged rather than escaping, so they're really diagnostics, but their unstructured message still ends up in logs, so it's still worth flagging. I need to pin down the exact diff line numbers for the new AgentSessionImporter.ts file—the invariant error construction spans roughly lines 63-78, so I'll comment on that range before posting the review.

7. modify_pr — Reviewed the new/changed Effect service code (AgentSessionImporter.ts, AgentSessionScanner.ts, ws.ts wiring, contracts errors). Dependency acquisition now happens through the environment (yield* Foo.Foo) and the RPC handler provides the services at the boundary, which matches the conventions. One error-modeling issue below.


Agent Credits: 101 credits

Token usage: 12 uncached input · 410,738 cache-read input · 89,959 cache-write input · 7,660 output
Agent Credits may also include non-token charges from external tools such as web research.