Repository navigation
Effect Service Conventions: 2 issues found
Two convention violations found, both in apps/desktop/src/app/DesktopDeepLink.ts:
- Hidden dependency in a module global (lines 75-107):
earlyCaptureis process-wide mutable module state holding the Electronappsingleton, and the service'sconfigureconsumes it viadrainEarlyOpenUrls(), so the early-capture buffer is an invisible input to the service and shared across tests. Suggested fix: return the capture handle fromcaptureEarlyOpenUrlsinmain.tsand inject it as a plain value (e.g. aContext.Referenceprovided withLayer.succeed). - Ambient environment read instead of environment acquisition (line 279):
process.argvis read directly inside the service, although@t3tools/shared/hostProcessalready exposesHostProcessArguments(aContext.Referencedefaulting toprocess.argv), which would add no requirement toconfigurewhile making the Windows/Linux cold-start path injectable and testable.
Both findings were posted as inline review comments.
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 scope
apps/desktop/src/app/DesktopDeepLink.ts(new service),DesktopDeepLink.test.tsapps/desktop/src/app/DesktopApp.ts,apps/desktop/src/main.ts,apps/desktop/src/preload.tsapps/desktop/src/electron/ElectronWindow.ts,apps/desktop/src/ipc/DesktopIpc.ts,apps/desktop/src/ipc/channels.tsapps/web/src/routes/__root.tsx,packages/contracts/src/ipc.ts, updated desktop test harnesses
Checked and found compliant
- Namespace subpath imports (
effect/Effect,effect/Layer, ...); named import for@t3tools/contracts; local service modules imported as namespaces. - Canonical module shape: inline interface on
Context.Service, realmake,export const layer = Layer.effect(...); mirrors the existingDesktopClerkservice. - Dependencies acquired from the environment (
yield* ElectronApp.ElectronApp,ElectronWindow,DesktopIpc,DesktopEnvironment) and reflected inconfigure's requirement type, includingScope.Scope. Effect.runPromiseWithis used only to bridge Electron OS-event callbacks, matching the established pattern inDesktopClerk.ts,DesktopWindow.ts, andDesktopIpc.ts; service instances are passed only in tests viaprovideService.ElectronWindow.fromWebContentsIdreuses the existingElectronWindowOperationErrorwith structured attributes pluscause, consistent with sibling operations;Effect.catchon IPC handler registration handles the entire (single-tag) error channel, which the conventions permit.- Tests cover new behavior rather than a mechanical refactor; harness updates to the new
fromWebContentsIdmember are mechanical.
π Agent activity β 15 tool calls
π I'm reviewing the new DesktopDeepLink.ts file against our conventions, checking the import style and confirming that the service definition follows the expected order of imports, pure helpers, and then the main service logic. Looking closer, I notice a module-level mutable variable (
earlyCapture) that holds state consumed later inside the service's configure logic β this looks like exactly the kind of hidden dependency our conventions warn against, since it's a module global rather than something passed through the environment. The justification is the macOS cold-start timing issue before the layer is constructed, but I still think this counts as a real violation worth flagging. Then there'srunPromiseused inside the service constructor's configure step. Normally that pattern would be flagged sincerunPromisebelongs at application boundaries, not inside domain services β but here it's actually inside the Electronon("open-url", ...)callback, which is a legitimate native OS callback boundary, so this usage seems acceptable rather than a violation. I also want to check whether other desktop services in the repo handle Electron's native callbacks similarly, so I should look at howElectronApp.onis typically wired up elsewhere. The mutablesubscribersarray in the closure seems like fine local state, not something that needs to be aRefsince it's not shared globally. TheEffect.catchon the ipc.handle registration looks reasonable too β it's meant to intentionally handle the entire error channel, and loggingerror.messagethere seems like standard practice rather than a clear violation of the sanitized-logging rule. No new error definitions show up in this section either. ForWebContentsId, I need to confirm the existingElectronWindowOperationErrorclass actually supports an "inspect-window" operation, and check whether wrapping a constructed error just to immediatelyorDieit fits the established pattern elsewhere inElectronWindow.ts. I want to pull up the actual source forElectronWindow.ts,ElectronApp.ts, andDesktopIpc.tsto verify these patterns directly rather than guessing. I'm also noting thatDesktopIpc's optionaleventparameter, and the deep-link handler'sevent?.sendercheck with a comment about it being absent only in test-fabricated events, is a minor test-accommodation detail rather than anything the conventions explicitly call out.
1. browse_code β apps/desktop/src/electron/ElectronApp.ts
2. browse_code β apps/desktop/src/electron/ElectronWindow.ts
π The ElectronWindow addition looks consistent with existing patterns. Now I want to check if other desktop services use
Effect.runPromiseWithinside listeners to see if there's precedent for that approach.
3. git_tools β runPromiseWith|runPromise|runFork
π This runPromise usage matches the existing convention for bridging native Electron callbacks, so it's not a finding. For the module-global earlyCapture, I want to check DesktopClerk.ts for a similar pre-layer capture pattern, like the OAuth deep-link early capture, to see if there's precedent.
4. browse_code β apps/desktop/src/app/DesktopClerk.ts
π I'm noting that DesktopDeepLink closely follows the pattern in DesktopClerk. Now I'm evaluating whether the module-level global for early-captured open URLs, populated at import time and drained later during configuration, counts as hiding a dependency rather than passing it explicitly. I'm also checking whether reading
process.argvdirectly inside configure bypasses an existing host process abstraction, and whether thesubscribersarray kept in closure state (rather than a Ref, unlikepending) is a meaningful inconsistency or just normal service-local state similar to howmainWindowRefis handled elsewhere. The test file's use of fake service instances constructed viaprovideServiceand typed with["Service"]looks fine and consistent with allowed test patterns, and the new tests are justified since they cover genuinely new behavior rather than a mechanical refactor. I'm also confirming that wrapping theipc.handleregistration errors withEffect.catchis appropriate since it fully handles that error channel by logging a bounded message, and I'm checking whetherDesktopIpc.handle's Scope requirement is properly threaded through configure's own requirements. Everything about dependency acquisition checks out against the DesktopClerk pattern β services are pulled from the environment viayield*rather than hidden. That leaves the module-global early capture as the real concern: since it's mutable state that configure depends on implicitly, it's a hidden dependency and a testability hazard, since leftover state from one test could leak into another. I think this genuinely violates the "don't hide dependencies in module globals" rule. The right fix would be to have the capture function return a handle and provide it through the environment instead β as a layer or passed directly into make β rather than reading from an untracked global. I should check whether there's precedent elsewhere in the codebase for module-levelletstate in desktop main-process services, and also whetherprocess.argvis read directly anywhere rather than through a proper host-process abstraction.
5. git_tools β ^let |^const [a-zA-Z]+: {|process.argv
6. git_tools β export
π There's a HostProcessArguments context reference for process.argv, so reading process.argv directly instead of yielding it from that service is a genuine hidden environment dependency, aligning with the principle that production services must acquire dependencies from the environment rather than module globals. I should check how HostProcessArguments is defined and used elsewhere to confirm.
7. browse_code β packages/shared/src/hostProcess.ts
8. git_tools β HostProcessArguments
π I notice there's already a Context.Reference for argv used server-side, but desktop's DesktopLifecycle.ts reads process.argv directly instead. Since a default value exists, switching to the Effect environment read would be trivially better and more testable, though it's not strictly a service dependency violation since process.argv is an ambient read rather than an injected service β existing desktop code already follows this same pattern. I'm also weighing how to interpret the ambiguous instruction about defaulting to failure versus outputting "All clear" β I need to reconcile that with the required JSON output format, so I'll treat "no findings" as a success state and reserve failure for actual clear violations. Let me weigh the strongest candidate findings before finalizing my decision. Looking at the module-level mutable global holding the Electron app source, this directly violates the convention against hiding dependencies in module globals or closures over singleton services β the
configurefunction reads this hidden state viadrainEarlyOpenUrls(), creating an undeclared input and fragile test-ordering dependency where a second capture call gets silently ignored. A convention-compliant fix would have main.ts pass the captured buffer explicitly as a value into the layer rather than relying on this hidden global. I'm weighing whether this is a hard failure or just worth flagging β the timing constraint genuinely does require pre-layer capture, so the global is arguably just the mechanism rather than a true hidden dependency, and reasonable reviewers might accept it with an explanatory comment. But since it does carry a singleton imperative API into service state, I lean toward treating it as a real violation worth failing on, even though it's debatable. I also want to check the optional event parameter on the DesktopIpc handler, though the conventions don't clearly address that case. Separately, I notice the subscriber logic mixes a Ref with a plain mutable array, and calls a side-effecting function directly inside an Effect.gen body rather than wrapping it in Effect.sync β a mild purity violation that isn't explicitly covered by the stated conventions either. I want to check if there's a repo-wide guideline file that addresses Ref usage or module-level globals to see if this strengthens the case.
9. browse_code β /{.macroscope/,AGENTS.md,CLAUDE.md,docs/dev/**}
10. browse_code β AGENTS.md
π I'll skip re-reading the check config since it's likely the same, and instead focus on double-checking DesktopDeepLink.ts for details like error handling and how ipc.handle interacts with Effect's catch semantics. Let me now look at DesktopIpc.ts as well.
11. browse_code β apps/desktop/src/ipc/DesktopIpc.ts
π Since DesktopIpcRegistrationError is the only tag in the error channel here, using Effect.catch to handle it entirely is consistent with the convention rather than needing catchTags. I want to see how other desktop services handle similar ipc.handle failures for comparison.
12. git_tools β ipc[.]handle|catchTags|Effect.catch
π I'm noticing there's already a DesktopLinuxUrlHandler.ts handling scheme registration and an ElectronProtocol, so I should check how they obtain the scheme from DesktopEnvironment to keep the new service consistent with existing patterns. Now weighing two possible findings: the module-level
earlyCaptureglobal being read as a hidden dependency insideconfigure, versusprocess.argvbeing read directly instead of going through the existingHostProcessArgumentsreference. The argv issue feels more concrete since there's already a Context.Reference precedent for it in the codebase, and using it would cost nothing while making the cold-start path testable β so I'm leaning toward flagging both together under one "hidden inputs to configure" comment. I'm checking whether these truly count as convention violations that should fail the check, since the rule requires verifying that runPromise/service-instance patterns are legitimate boundaries rather than fake dependency injection. The runPromise usage insideconfigurematches the established Electron native-callback pattern already used elsewhere in the repo, so that part looks legitimate β it's really the module global and argv access that remain the open question. Both findings share the same theme, so posting them together is reasonable. Let me also check whether the new IPC payload type should use Schema instead of a plain interface β looking at contracts conventions, other DesktopBridge payloads like MENU_ACTION_CHANNEL for comparison, though Schema usage seems out of scope for these particular Effect-service instructions. I also want to check whether the preload's manual validation instead of Schema decode falls under anything I'd normally flag, and whether there are any consolidated "effect" imports in the diff β there aren't. I'm also weighing the file layout order in DesktopDeepLink.ts against the canonical pattern of imports, error/schema declarations, the Context.Service tag, make, then layer β helpers appearing before the tag is a minor deviation similar to DesktopClerk, not worth flagging. Now I need exact line numbers in the diff for inline comments, since for a new file the whole thing is included and any line works. I'm locating the early-capture variable declaration and the process.argv reference in DesktopDeepLink.ts so I can pinpoint where to comment.
13. browse_code β apps/desktop/src/app/DesktopDeepLink.ts
14. browse_code β apps/desktop/src/app/DesktopDeepLink.ts
π I'll leave two inline comments: one flagging the hidden module-global dependency and suggesting explicit injection of the capture handle for testability, and another suggesting the process.argv usage be replaced with
HostProcessArgumentsfrom the shared hostProcess module so it flows through the Effect environment instead. Since neither has an obvious direct patch (both cross cross-file or multi-hunk boundaries), I'll describe the fixes rather than provide diffs. Given the module-global comment is a genuine convention violation, I'll mark this review as a failure. I'll keep both comments concise, and note the correct line references for GitHub's line-comment API β lines 75-79 for the first, line 279 for the second. For the first comment, I'm explaining that the early-capture buffer is invisible module state consumed by configure, causing silent test pollution, and suggesting the capture handle be threaded through explicitly as a Context.Reference value instead. For the second, I'm noting that reading process.argv directly from the ambient global inside the service hides an environment dependency that should be acquired the same way. Now I'm confirming the review submission has the right body without needing an explicit event param, and double-checking that the docs file stays out of scope for this review.
15. modify_pr β Effect service conventions review: two findings in apps/desktop/src/app/DesktopDeepLink.ts, both about inputs to the service that never appear in the Effect environment. The service shape, make/layer naming, dependency acquisition via yield* Foo.Foo, and the native-callback runPromiseWith bridge all match the existing DesktopClerk/DesktopWindow patterns.
Agent Credits: 131 credits