Skip to content

feat(web): import recent Codex and Claude threads during onboarding

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

Effect Service Conventions: 1 issue found

apps/server/src/ws.ts (lines 1966-1995) - The new agentSessions.import handler acquires ProviderSessionDirectory with Effect.serviceOption and fails with a hand-built AgentSessionScanError({ operation: "read-projects", cause: new Error("Provider session storage is not available.") }) when it is missing. That service is merged into the same layer context as ProviderService (ProviderLayerLive does Layer.provideMerge(ProviderSessionDirectoryLayerLive)), so the optionality is fake: it hides a real requirement from the layer type, manufactures an Error purely to fill cause, and reuses a read-projects operation label that renders the misleading caller-visible message "Failed to scan agent sessions during read-projects." Fix by acquiring the service with yield* ProviderSessionDirectory.ProviderSessionDirectory and removing the Option.match branch.

An inline review comment was posted on the affected lines.

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 changes: apps/server/src/project/AgentSessionImporter.ts (+ test), apps/server/src/project/AgentSessionScanner.ts (+ test), apps/server/src/orchestration/decider.ts (+ new test), apps/server/src/orchestration/Layers/CheckpointReactor.ts, apps/server/src/auth/RpcAuthorization.ts, apps/server/src/ws.ts, packages/contracts/src/{agentSessions,orchestration,rpc}.ts, apps/web/src/state/agentSessions.ts, apps/web/src/components/onboarding/WelcomeWizard.tsx.

Checked and found acceptable:

  • Namespace subpath imports (effect/Effect, effect/Crypto, effect/Schema) and namespace imports of local service modules (AgentSessionScanner.AgentSessionScanner, OrchestrationEngine.OrchestrationEngineService, ProviderSessionDirectory.ProviderSessionDirectory); named imports from @t3tools/contracts are correct per the conventions.
  • AgentSessionScanner service shape extension (recentThreads) stays inline in the Context.Service declaration; make/layer unchanged in the canonical module.
  • importRecentAgentThreads acquires all dependencies from the environment (yield* ...); no service-instance parameters, no ManagedRuntime.make, no runPromise/runPromiseExit introduced in server code. Service instances are passed explicitly only in the new tests, which is an allowed test seam.
  • Effect.catch around the per-thread import intentionally handles the whole error channel to skip and count a bad session, with the underlying cause preserved in the warning log.
  • Backend behavior changes are covered by focused tests (decider.import.test.ts, AgentSessionImporter.test.ts, scanner recentThreads cases, checkpoint suppression for historyImport).
  • The mutable cachedCandidates closure in AgentSessionScanner.make is a value cache, not a hidden dependency, and matches existing patterns such as UsageService; not flagged.

🔍 Agent activity — 19 tool calls

💭 I'm looking at the new files—AgentSessionImporter, the scanner changes, and ws.ts—checking for issues like a mutable cache variable inside the scanner's closure, which might count as hiding state that should instead be an explicit dependency. I'm also weighing whether catching the entire error channel in the importer is appropriate here, since it's used to skip failed sessions and log the cause safely. Then I'm turning to ws.ts, where it uses serviceOption for the provider session directory and fails with a specific error when session storage isn't available. Now the concrete issue jumps out: the code fabricates a bare Error object just to fill the cause field, which violates the convention against manufacturing errors, and the operation label used there is also inaccurate. I want to double-check the actual error schema definition to confirm whether cause is even required, and I'm also verifying that providing four services at the RPC handler boundary and the import statements in the session importer are both consistent with normal patterns in this codebase.

1. git_tools — AgentSessionScanError

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

💭 I'm checking ws.ts to see how other RPC handlers deal with missing optional services, looking for existing patterns like throwing errors.

3. git_tools — is not available

4. git_tools — cachedCandidates|normalizeProjectPathForComparison

💭 Looking at ws.ts, there's another violation: manufacturing an Error just to populate cause, and mislabeling the operation as "read-projects" for what's actually a service-availability failure — the resulting message would misleadingly claim the failure occurred during "read-projects." Wait, let me check if there's precedent elsewhere in ws.ts for handling optional services this way before concluding it's a genuine violation.

5. git_tools — serviceOption

💭 I'm considering whether ProviderSessionDirectory is truly optional in this layer or always present in production, since using serviceOption with a fabricated error would be questionable if it's guaranteed. I'm weighing this against the convention that unavailability should be modeled explicitly through service operations or an optional-service layer so downstream recovery stays visible and testable. The real concern is that this approach hides the handler's dependency by not surfacing it in the layer's requirements. I should check whether ProviderSessionDirectory is actually part of the server runtime layer's provided context to settle this.

6. git_tools — ProviderSessionDirectory.layer|ProviderSessionDirectory.

💭 I'm checking whether ProviderSessionDirectory is genuinely optional in production or whether using serviceOption here just masks a real dependency that should be declared explicitly in the ws layer's requirements.

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

💭 I'm wondering why ProviderSessionDirectory alone uses serviceOption instead of direct yield acquisition—maybe it's not present in the ws layer's context in production. I want to check where the ws layer is composed in server.ts and trace where makeWsRpcLayer's provided layers actually come from.

8. git_tools — makeWsRpcLayer(

💭 I'm wondering whether ProviderSessionDirectory is guaranteed to be in the ambient context of the ws layer, since using serviceOption suggests it might only be available in certain deployments. I should check server.ts to see how the environment actually provides it.

9. git_tools — ProviderSessionDirectoryLive

💭 I want to check if ProviderSessionDirectoryLayerLive gets merged into the app layer so the ws router has access to it, so I'll search for that in server.ts.

10. git_tools — ProviderSessionDirectoryLayerLive

💭 Since ProviderLayerLive merges the directory alongside ProviderService, the directory should already be available wherever ws.ts pulls in ProviderService. That means the serviceOption fallback with a fabricated AgentSessionScanError is really just masking a real dependency instead of handling genuine optionality. I should check whether swapping in a direct yield* for ProviderSessionDirectory would actually type-check given how the router composes layers -- the requirement would flow into the layer's context and need to be satisfied by the provider layer already present at the top level, so I need to trace whether that context reaches the ws layer construction point. Since ProviderService itself already yields directly here and its own layer merges the directory service, the context should already contain it, so the serviceOption workaround looks unnecessary — I want to confirm by grepping server.ts for how the router layer and its providers are wired together.

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

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

💭 Tracing where websocketRpcRouteLayer is used in server.ts, ProviderService gets yielded directly in the ws rpc layer, so its context should include ProviderSessionDirectory too since ProviderLayerLive merges it in — meaning the optional service check might be unnecessary. Still, I should double check whether other consumers, like a test harness or cloud entry point, provide ProviderService without that directory.

13. git_tools — websocketRpcRouteLayer

💭 Since ProviderSessionDirectory is only used once in server.ts within the same layer as ProviderService, using Effect.serviceOption is unnecessary here—it can be acquired directly, which is a legitimate finding on dependency acquisition conventions. Now I want to look more closely at AgentSessionScanner.ts, particularly the mutable cachedCandidates closure, so I'll check lines 620-800.

14. browse_code — apps/server/src/project/AgentSessionScanner.ts

15. browse_code — apps/server/src/project/AgentSessionScanner.ts

💭 I'm considering whether the cached candidates closure should be a Ref instead of plain mutable state—it's not really a hidden dependency, but there's a subtler issue: recentThreads can reuse stale candidates from a previous scan call since the cache just gets overwritten, not merged. Since this scanner layer is per-connection anyway, the staleness feels more like a design tradeoff than a listed convention violation, so I'll skip flagging it and stay focused on concrete issues. Turning to AgentSessionImporter's use of Effect.catch, I want to check whether it's swallowing errors that shouldn't be lumped together with the intentional per-thread skip logic. Logging the raw cause in log annotations seems fine since it's preserving the error rather than fabricating detail text. The importer being a plain exported function rather than a service is also acceptable as a helper. I'm going through decider.ts, CheckpointReactor, and the web state file, and none of these raise new concerns. The legacy ProviderSessionDirectoryShape type isn't part of this change, so I'll leave it alone. What does stand out is AgentSessionScanError using operation: "read-projects" — the message text doesn't quite match, which could be a real semantic mismatch worth flagging. I want to suggest acquiring ProviderSessionDirectory directly instead of the fake-optional branch, but I need to verify this won't break the layer requirements — if the service isn't in scope it'll fail typechecking. I should check whether other ws handlers rely on the provider directory similarly, and look at how the layer stack is assembled in server.ts to confirm ProviderLayerLive is actually provided to the router layer group.

16. browse_code — apps/server/src/server.ts

💭 The rest of makeRoutesLayer's requirements come from further up the stack, likely in makeServerLayer or the app layer. Since the ws layer already needs ProviderService and ProviderLayerLive merges ProviderSessionDirectory into that same context, requiring ProviderSessionDirectory should work out fine. I should double-check that ProviderLayerLive actually gets provided to the routes layer somewhere.

17. git_tools — ProviderLayerLive

💭 Good, that confirms ProviderRuntimeLayerLive merges the provider layer with the directory, so both services share context and direct acquisition works — finding 1 is confirmed as a fake optional dependency plus fabricated error cause and mislabeled operation. Now I'm checking the Crypto usage in the ws code, where a service instance from the environment is being provided directly, which looks fine so far. Providing the four services via provideService at the handler call site matches how this codebase normally works, like in reconcileProviderSessions, so nothing to flag there. The AgentSessionScanner's recentThreads failure type looks correct too, and the placement of pure helper functions like extractText and parseAgentSessionTranscript between the tag and make isn't unusual given the existing file layout. I'm also noting a mutable cachedCandidates variable sitting between collectCandidates and scan inside make, which is worth keeping an eye on as potential state. The mutable let cache isn't a clear-cut convention violation since it's not really a hidden dependency, just local state that might ideally use a Ref for concurrency safety, but that's a style preference not worth flagging. I want to check whether other services in the repo already use plain let mutable caches to see if this is established practice before deciding.

18. git_tools — ^ let [a-zA-Z]+.*= null;

💭 The UsageService cache pattern is already established elsewhere, so I'll skip flagging that. That leaves one finding to write up as an inline comment on ws.ts around lines 1969-1976, targeting the error construction, with a suggested fix replacing the service option logic. Since the fix spans multiple hunks (the acquisition point and the handler), I'll skip providing a diff and just describe the change in prose: noting that ProviderSessionDirectory is already merged into the same layer as ProviderService, so treating it as optional is misleading and produces a fabricated failure message. I'll suggest acquiring it directly instead of using Option.match, simplifying the handler. I'm also weighing whether swallowing errors elsewhere in AgentSessionImporter counts as a violation, but that seems justified since the entire error channel is intentionally handled there. Given the confirmed convention violation around fabricating an Error just to populate cause, I'll mark this review as a failure and post it.

19. modify_pr — One finding: the new agentSessions.import handler treats ProviderSessionDirectory as an optional service and fabricates an AgentSessionScanError when it is absent, even though that service is provided in the same context as ProviderService. See the inline comment.


Agent Credits: 144 credits