Skip to content

fix(desktop): claim the t3code:// scheme default on Linux at startup

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

Effect Service Conventions: 2 issues found

Two error-modeling convention violations in apps/desktop/src/app/DesktopLinuxUrlHandler.ts:

  • Manufactured Error to satisfy a required cause (lines 30-32 / 135-139): the non-zero xdg-mime exit has no underlying failure, so new Error("xdg-mime exited with code ...") is created purely to populate cause: Schema.Defect(). The exit code should be a structured attribute (exitCode) with cause made optional, matching apps/server/src/cloud/pinnedRuntime.ts and bootService.ts.
  • Missing path context on the write step (lines 111-118): writeDesktopEntry wraps makeDirectory + writeFileString in a generic error carrying only step and scheme, discarding the desktop-entry path that is known at the wrapping site (compare the DesktopConnectionCatalogStore* errors, which carry path/catalogPath).

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.

Conventions verified as compliant:

  • Namespace subpath imports for all Effect modules (effect/Effect, effect/Layer, effect/Schema, effect/unstable/process/*); local service modules imported as namespaces (DesktopEnvironment, ElectronProtocol); makeComponentLogger kept as a named import for a pure helper.
  • Canonical single-file order: imports, error declaration, Context.Service tag with inline interface, make, layer; no standalone FooShape type; export const layer = Layer.effect(DesktopLinuxUrlHandler, make) with a real make.
  • Dependencies acquired from the environment (yield* DesktopEnvironment.DesktopEnvironment, FileSystem.FileSystem, ChildProcessSpawner.ChildProcessSpawner); no service-instance parameters, ManagedRuntime.make, or runPromise/runPromiseExit in the service.
  • Layer.succeed occurrences appear only in the test file as legitimate test seams; the test uses DesktopEnvironment.DesktopEnvironment["Service"] for the inferred interface.
  • step is a multi-value discriminator feeding one generic message, so no redundant singleton discriminator; Effect.catch is used where the whole error channel is intentionally handled (best-effort registration), which the conventions permit.
  • Behavior change (new Linux scheme registration) is covered by focused tests, so the added test file is appropriate rather than test padding.

Files examined: apps/desktop/src/app/DesktopLinuxUrlHandler.ts, apps/desktop/src/app/DesktopLinuxUrlHandler.test.ts, apps/desktop/src/app/DesktopEnvironment.ts, apps/desktop/src/app/DesktopConfig.ts, apps/desktop/src/app/DesktopApp.ts, apps/desktop/src/main.ts, plus repository-wide error-schema patterns for comparison.


🔍 Agent activity — 1 tool call

💭 I'm reviewing the DesktopLinuxUrlHandler.ts file to ensure it follows the established conventions—checking that imports use namespace syntax from effect subpaths, the service definition follows the right order with imports, constants, logger, errors, and the Context.Service tag, and verifying the structure matches the pattern. Looking at the error handling, I notice isRegistrationError is a private Schema.is constant rather than an exported predicate, which slightly deviates from the convention of exporting direct schema predicates, though it's not wrapped in a redundant function so it's a minor concern. The error class itself uses Schema.TaggedErrorClass with the appropriate fields like step, scheme, and cause, with the message derived from attributes, which is correct. For the error handling patterns, Effect.catch is being used to intentionally handle the entire error channel with a warning log, which is appropriate. Then Effect.mapError wraps the multi-step operation (directory creation and file writing) to transform any errors into the custom DesktopLinuxUrlHandlerRegistrationError. However, there's a gap here: the error class captures the step and scheme context but doesn't preserve the actual file path that's available at the wrapping site (linuxApplicationsDir and the entry path). According to the conventions, stable domain context like resource paths should be captured in the error. That's a legitimate issue — the path information is known but lost. For setDefaultHandler, the error mapping uses a pass-through pattern where existing registration errors are preserved while others are wrapped, which is fine, though it could potentially be simplified by applying mapError only to the spawn operation itself. Looking at the dependency setup, the layer correctly yields DesktopEnvironment, FileSystem, and ChildProcessSpawner through the closure, so the register effect doesn't need external requirements. The test file appropriately adds tests for this behavior change rather than just mechanical refactoring. Now I need to verify some conventions in the actual codebase — checking how other services model errors, whether DesktopEnvironment.path follows an existing shape, and confirming that ElectronProtocol.getDesktopScheme exists and aligns with platform-specific patterns already in use. I'm looking at conventions in similar code to see how other services handle Schema.is exports and error predicates. It seems the pattern is to export direct schema predicates like export const isFoo = Schema.is(Foo), but here isRegistrationError is private and wraps a Schema.is constant with a redundant function that has the same signature — that's the issue to flag. For the write-desktop-entry error, the convention says to retain path context when available, but here step and scheme might not be enough since the actual path is deterministic from the environment. Though wrapping makeDirectory and writeFileString into a single logical operation seems reasonable. The real concern is whether setDefaultHandler is properly re-checking the error with isRegistrationError after it's already been raised. There's also a solid issue with how the non-zero exit code from xdg-mime is handled — they're manufacturing a new Error just to populate the cause field, which violates the convention that you shouldn't create an Error merely to fill a required cause unless there's an actual underlying failure. The exit code itself isn't a failure that needs wrapping; it's just a status code. The better approach would be to capture the exit code as a structured field in the error schema and make cause optional instead. This also reveals another problem: the log message doesn't include the exit code, so that information gets lost entirely since only the message is logged, not the cause. Looking at the rest of the code, the test setup with the layer seam and the error handling with Effect.catch look fine. The logging is also properly sanitized, pulling from structural attributes. I'm noticing the make function uses Effect.gen here, whereas other services in the repo seem to use Effect.fn — worth checking if there's an inconsistency in the pattern. Now I'm searching through the codebase to see if there's an existing convention for error schemas that include cause: Schema.optional(Schema.Defect()). The repo's convention for handling exit codes favors capturing it as a structured field with an optional cause property, so manufacturing an Error just to satisfy that requirement violates the principle against creating errors for schema compliance and obscures the actual exit code information. I should also look for a second issue around missing path context in the write-desktop-entry error, since the codebase typically includes file paths when reporting filesystem errors. I'll consolidate into a single comment on the Error instantiation (lines 135-139) that covers both the exitCode field addition and making cause optional, then add a brief separate note about the write path. Let me search the desktop codebase to see if there's already a pattern for error fields with path information I should follow. The repo pattern is showing up consistently in filesystem errors, so both findings check out. Now I need to write the review with two inline comments—one targeting the error field definition around lines 30-32 and another on the manufactured Error at line 138, where I'll need to add an exitCode field and optional cause. Since this fix spans multiple sections, I'll anchor the comment on the return block at lines 135-139. For the second comment, I'm flagging the write-desktop-entry error that's missing the actual desktop entry path in the message, anchoring that around lines 111-118. Let me draft these comments now, starting with the error structure issue. Since we found violations, I need to return a JSON response indicating failure rather than the "All clear" message.

1. modify_pr — Reviewed the new DesktopLinuxUrlHandler service against the Effect service conventions. The service/tag/make/layer shape, namespace imports, dependency acquisition (yield* Foo.Foo), and Effect.catch on a fully-handled channel all look right. Two error-modeling issues below.


Agent Credits: 94 credits