Skip to content

feat(cli): open projects in the running desktop app

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

Effect Service Conventions: 3 issues found

Three convention findings were posted as inline review comments.

  • apps/desktop/src/app/DesktopAppActivation.ts:215 — make is not exported; every other desktop service module exports construction as export const make alongside the tag and layer.
  • apps/desktop/src/app/DesktopAppActivation.ts:225 (and :255) — the service constructor reads process.getuid?.() directly instead of acquiring HostProcessUserId from the Effect environment, hiding the dependency from make/layer requirements; the PR's own test and apps/server/src/cli/app.ts already use that reference.
  • apps/server/src/cli/app.ts:160-163 — the Effect.tryPromise catch drops the underlying socket failure, so the immediate error is not preserved as cause; a fix keeping the user-facing message while chaining the real cause was suggested.

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: all changed apps/** and packages/** TypeScript files, plus the existing service modules they interact with (DesktopWindow.ts, ElectronWindow.ts, packages/shared/src/hostProcess.ts, apps/desktop/src/main.ts layer composition, apps/server/src/bin.ts).

Items checked and found acceptable:

  • Namespace subpath imports for Effect modules (effect/Effect, effect/Layer, effect/Context, effect/Schema, effect/Path, effect/Scope) and namespace imports for local service modules (DesktopWindow, ElectronWindow, DesktopEnvironment); named imports are limited to @t3tools/contracts, pure helpers (resolveDesktopAppControlAddress, makeComponentLogger), and channel constants.
  • Context.Service tag with an inline interface (no standalone FooShape), export const layer = Layer.effect(...), and no compatibility shims or leftover modules.
  • DesktopAppActivationStartError uses Schema.TaggedErrorClass with a structural address field plus a real cause, and derives message from address only.
  • Effect.runPromiseWith inside make bridges the Effect services into imperative Electron/Node socket callbacks, matching the existing DesktopWindow adapter pattern; it is not consumed by another Effect service.
  • Socket lifetime is owned by Effect.acquireRelease with a scoped start, and Layer.provide(desktopWindowLayer) reuses the single application-owned DesktopWindow layer rather than a per-feature runtime.
  • Effect.catch in DesktopApp.ts handles the whole error channel, so catchTags is not required.
  • New tests cover real behavior (socket round-trip and cleanup, broker queueing/cancellation/timeout, CLI request routing), not a mechanical refactor.

🔍 Agent activity — 11 tool calls

💭 I'm looking at the DesktopAppActivation.ts file, which defines a service using Context.Service with a make constructor and layer, and exports a Node server adapter. I'm checking whether the runPromise call inside the service constructor is being used correctly at a proper boundary rather than violating Effect conventions internally. The tricky part is that this runPromise bridges into an imperative broker invoked from a Node socket callback, which could be seen as a legitimate native callback boundary. But the guidance warns that this imperative bridge must remain isolated and not become a dependency injected into other Effect services, so I'm evaluating whether the broker creation here stays properly contained within the service. I'm also noticing the service directly calls process.getuid?.() rather than pulling it from the environment via an Effect service like HostProcessUserId, which the CLI does use elsewhere in shared/hostProcess -- that looks like a bypass of the established service dependency pattern. I want to check what other host process services exist for things like tmpdir before flagging this, and I should also verify that the DesktopAppActivationStartError schema correctly derives its message from the address field and defect cause. Checking the pure helper import for resolving desktop app control addresses and the activation channel import — both look fine. I'm also verifying the error handling pattern in DesktopApp.ts, confirming the effect library API usage matches how this repo structures error catching, then moving on to inspect how DesktopAppActivation starts up. I notice make isn't exported from this service module, even though the module owns construction — that seems to break the repo's convention, so I want to compare against how DesktopWindow.ts handles the same pattern before flagging it. I'm also weighing whether the mutable closure state for tracking registered web contents and detaching renderer listeners is acceptable given the service design conventions here. I also want to check whether DesktopAppActivation.ts calling process.getuid directly conflicts with the existence of a dedicated HostProcessUserId service, and similarly whether NodeOS.tmpdir should be routed through a service instead — worth digging into the hostProcess module to confirm.

1. browse_code — packages/shared/src/hostProcess.ts

2. browse_code — apps/desktop/src/window/DesktopWindow.ts

💭 I'm noticing DesktopAppActivation.ts doesn't export its make, breaking the convention that modules owning construction should export a real make. There's also a hidden dependency issue: the desktop service calls process.getuid?.() directly instead of going through the HostProcessUserId service reference used elsewhere in the CLI and tests, violating the convention that production service construction should acquire Effect dependencies properly rather than reaching into imperative APIs. I'll call it a legitimate but soft finding. Now I want to check the CLI app.ts helper that wraps errors into a UserError with a manufactured cause—the rules discourage helpers that just construct an error object or fabricate an Error solely to populate cause, so I should see how other CLI commands construct UserError for comparison.

3. git_tools — CliError.UserError|new UserError

4. git_tools — export const make

💭 I'm confirming the convention of using export const make across desktop service modules, and noting that DesktopAppActivation.ts breaks this pattern with a private const. Now I want to check how other CLI commands construct user-facing errors, so I'll look through the CLI files in apps/server/src/cli.

5. git_tools — UserError

💭 Since this is the only usage in the new file, I want to check how other CLI files across the repo signal user errors, so let me search apps/server/src for CliError more broadly.

6. git_tools — CliError

💭 I'm weighing whether desktopAppCommandError counts as the kind of trivial wrapper helper the guidelines warn against, since it just wraps a message in an Error and constructs the CliError—though it's used several times in the file, which might justify its existence despite matching that anti-pattern. But the more concrete issue I'm spotting is in app.ts: the catch handler in Effect.tryPromise builds a new UserError via desktopAppCommandError but completely discards the actual socket error that caused the failure, violating the convention that wrapped errors should preserve their real underlying cause. Now I'm zeroing in on the two strongest, most defensible findings: the unexported make in DesktopAppActivation.ts, which breaks convention, and the direct use of process.getuid?.() in the service rather than pulling HostProcessUserId from the Effect environment — a hidden runtime dependency that shows up in two places and conflicts with how the PR's own tests inject that value as a service. I'm also weighing whether Effect.runPromiseWith inside the service's make deserves a flag, but DesktopWindow already does this at electron callback boundaries, so it's consistent with existing precedent and I'll leave it alone. The CLI's swallowed cause in the Effect.tryPromise catch still stands out as a real issue worth flagging, since it silently replaces the actual socket failure with a manufactured error instead of preserving it as the cause. A bigger refactor feels risky, so instead I'm thinking of a smaller fix: wrap the underlying cause in a new Error with the friendly message and pass the original as its cause, so CliError.UserError still preserves the full error chain while keeping the guidance text intact. I want to confirm the exact lines in app.ts affected, and check that the message-rendering path still works with this nested cause structure — it should, since the original message stays visible. I'm deciding to skip flagging the desktopAppCommandError helper itself since it's used consistently across four call sites and mainly does message normalization, so I'll keep my comment focused just on the lost cause issue. I'm also glancing at the web-side files (DesktopAppActivationCoordinator.tsx and desktopAppActivation.ts) since those are plain TS/React without Effect services, and I notice a failure(...) call returning a plain object with a hardcoded version number rather than referencing the shared constant. I'm checking a few more spots — the entities.ts waitForProject function using setTimeout and promises seems fine given it's in the app boundary layer, and the contracts file with schemas plus index.ts export ordering both look fine too. Then I look at the layout of the desktop activation file itself: imports, error declaration, a helper function, the Context.Service tag, make, then layer — the tag comes after the helper rather than right after the error declaration, which is a bit off from the canonical order, but I don't think it's significant enough to flag since other files have similar helper placement. Checking a few other minor spots now: an import in the IPC handlers file that's out of alphabetical order, which seems more like a lint nitpick than an actual convention violation, so I'll leave it be. Then I move on to look at the layer composition in main.ts. I'm tracing whether desktopAppActivationLayer's composition, which provides desktopWindowLayer into the activation layer, could create a duplicate instance of DesktopWindow if that same layer is also merged separately into the final application layer -- though Effect's layer memoization by reference within a single build likely prevents that as long as the same layer instance is reused. I want to double-check against the convention against smuggling the same owned resource into multiple consumers via separate managed runtimes, so I'm going to look at how the other layers are composed in main.ts around lines 150-200.

7. browse_code — apps/desktop/src/main.ts

8. browse_code — apps/desktop/src/app/DesktopAppActivation.ts

💭 I'm confirming the effect typing is correct since the context is already narrowed to never for both activate and logWarning, and noting the layer provisioning difference between provideMerge and provide is fine since provide just hides the dependency. Now I'm moving on to identify the first finding. For finding 1, make isn't exported in DesktopAppActivation.ts even though every other desktop service module follows the convention of exporting a real make when it owns construction, so I'm suggesting an inline fix to export it. For finding 2, the service constructor is calling process.getuid?.() directly at two separate lines instead of pulling HostProcessUserId from the environment like the PR's own tests and CLI do—since it's a Context.Reference with a default, no layer changes are needed, just yielding the dependency and reusing it in both spots, though since this spans two locations I'm weighing whether to present it as a unified diff or separate suggestions.

9. git_tools — getuid|HostProcess

💭 There's one existing precedent using direct uid access, but the repo convention and this PR's own tests favor the HostProcess references, so I'll flag it as a moderate suggestion rather than a hard blocker. Since the check defaults to failure and I have findings, I need to post inline comments and decide on the overall review state. The missing make export is a clear convention violation seen consistently across ~35 desktop modules, and the instructions explicitly allow failing for clear violations, so I'll set the check to failure while keeping findings minimal, around 2-3 comments. Now I want to pin down the exact line numbers for the CLI cause finding by checking the relevant portion of apps/server/src/cli/app.ts.

10. browse_code — apps/server/src/cli/app.ts

💭 I'm planning to post comments on the desktop make export and getuid issues, plus flag the CLI case where the catch block silently drops the connect/timeout error instead of preserving it as the cause, per the errors-and-predicates convention. I need to make sure the comment line numbers are accurate before posting. Rather than touching the helper function across hunks, I could instead suggest wrapping the cause in a self-contained CliError.UserError with a nested Error preserving the cause chain, since CliError is already imported at that point. This keeps the fix scoped to the single hunk while still surfacing the original error properly. Since this CLI adapter consumes Effect services, the error convention guidance about preserving diagnostic causes still applies here, even though it's not a service definition itself — I'll flag it as a suggestion rather than a blocking issue. Now I'll post the review.

11. modify_pr — Effect service conventions review of the new DesktopAppActivation service and the t3 app CLI command. Three findings, all in apps/desktop/src/app/DesktopAppActivation.ts and apps/server/src/cli/app.ts. The contracts schemas, the broker, and the renderer coordinator follow the conventions (namespace subpath imports, inline Context.Service interface, structured Schema.TaggedErrorClass with address + cause, Effect.acquireRelease for socket ownership).


Agent Credits: 162 credits

Token usage: 18 uncached input · 782,744 cache-read input · 137,050 cache-write input · 11,632 output
Agent Credits may also include non-token charges from external tools such as web research.