Skip to content

fix(server): preserve telemetry error structure

MacroscopeApp / Macroscope - Effect Service Conventions failed Jul 29, 2026 in 3m 21s

Effect Service Conventions: 2 issues found

Two error-modelling convention violations remain in the new resource-telemetry service code.

  • apps/server/src/resourceTelemetry/NativeTelemetryClient.ts:648 — a decoder/stream termination is encoded by fabricating NativeTelemetryExited({ exitCode: -1 }), so a non-exit failure surfaces as Resource monitor exited with code -1. in caller-visible health. A distinct error (as the sibling receiver already models with DesktopTelemetryStreamClosed) keeps exitCode a real exit status.
  • apps/server/src/resourceTelemetry/NativeTelemetryClient.ts:717 — lastError: Option.some(Cause.pretty(cause)) copies arbitrary defect text and stack traces into a caller-visible field (ResourceTelemetryHealth.native.lastError, rendered in the diagnostics UI and mapped into the legacy process-resource-history error). The pretty cause belongs in the log annotation only; the stored value should be bounded and normalized.

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.

Scope reviewed: the full diff between 3137c2b and c779e8b, focused on Effect service modules and their call sites under apps/**, packages/**.

Checked and found conforming:

  • New service modules (BackgroundPolicy, HostPowerMonitor, NativeTelemetryClient, DesktopTelemetryReceiver, ResourceTelemetry, ResourceAttribution, ResourceMonitorBinary, DesktopTelemetryPublisher, ElectronPowerMonitor) follow the canonical single-file order, inline Context.Service interfaces, Foo["Service"] references, exported make, and a single canonical layer.
  • ProviderEventLoggers drops the standalone ProviderEventLoggersShape, exports make/layer, and every consumer (server.ts, tests) was updated; no compatibility shim left behind.
  • Production construction acquires dependencies via yield* Foo.Foo (e.g. makeManagedServerProvider now exposes BackgroundPolicy/ServerSettingsService in its requirements); service instances are only passed explicitly in tests/layerTest helpers.
  • No ManagedRuntime.make, runPromise, or runPromiseExit introduced in domain services; Layer.succeed uses are platform adapters or test layers.
  • Effect.catch uses handle whole error channels; no catchTag/schema-predicate catchIf recovery of known tags was introduced.
  • Namespace imports for local service modules and named imports for whole packages (@t3tools/contracts) and pure helpers (RpcAuthorization, backgroundActivitySettings) match the conventions.

Prior-run findings verified as resolved: structured ResourceMonitorBinaryError now propagates instead of being flattened into a free-text reason; request timeouts are modelled by NativeTelemetryRequestTimedOut (operation + timeoutMs, no fabricated cause); the unused ProcessIdentityChanged class and diagnostics error union were removed.


🔍 Agent activity — 1 tool call

💭 Looking at the PR diff to see what's changed since the last commit and whether the previous issues were resolved. The branch was rebased, so the previous commit dfd4462 is no longer in the history. The current head is c779e8b, and the latest commit about preserving telemetry error structure looks like it might have already addressed the issues flagged before. I should check the diff on the files I previously flagged to see what's changed. Looking at the PR review findings, the first issue with NativeTelemetryClient.ts is already resolved—the runAttempt function properly propagates the ResourceMonitorBinaryError through the structured error handling. The second finding appears to be cut off, but I'm checking what was flagged on ProcessDiagnostics.ts in the current head. The ProcessIdentityChanged error class that was flagged as unused has been removed, and the identity-mismatch logic now returns inline message strings directly. So that prior finding is addressed. Now I'm scanning the current diff for any new convention violations introduced in the three additional commits, focusing on areas like error handling patterns in ProcessDiagnostics.ts where error messages are being constructed. The NativeTelemetryUnavailable error stores an unstructured reason string derived from another error's message, which mirrors the earlier issue I identified — the lastError field gets passed through as a free-text string rather than being decomposed into structured attributes. This feels like the same pattern violation appearing at a different point in the codebase. I'm weighing whether to flag the reason field as a violation—it's borderline since it carries no structured context, but I don't want to re-flag something adjacent to a prior fix the author already addressed. Instead, I should focus on clearer violations: namespace versus named imports, Layer.succeed masking runtime behavior, ManagedRuntime patterns, catchTag/catchIf usage, service instance parameters, Effect.succeed assignments, and standalone Shape interfaces. Looking at the diff now, I notice DesktopBackendOutputLogShape is still there in the desktop observability file, though that appears to be pre-existing. In ProviderEventLoggersShape, they've removed the standalone interface and inlined it directly into the Context.Service declaration, renamed the live implementation to layer, and exported make—that's the right direction. The EventNdjsonLogger file isn't a service module, so different rules apply there. Looking at RpcAuthorization.ts, the requiredScopeForRpcMethod function throws a plain Error rather than using Effect, but that's fine since it's a pure lookup used during RPC handler construction—it's catching programmer errors, not service failures, and the tests expect that behavior. HostPowerMonitor.ts follows the canonical pattern with imports, tag, and a make function wrapped in Layer.effect. BackgroundPolicy.ts looks good too. In DesktopTelemetryReceiver.ts, there's a layerTest that seems to be getting lengthy. The error types here preserve causes without redundant fields—DesktopTelemetryDecodeFailed wraps unknown errors with the cause intact, and DesktopTelemetryControlFailed tracks the operation ("encode" or "write") along with the cause. The normalizeReceiverError function handles unknown errors by wrapping them into DecodeFailed. NativeTelemetryClient.ts follows the same pattern with NativeTelemetryCommandFailed tracking the operation and cause. But NativeTelemetryExited uses exitCode -1 as a sentinel to signal stream completion rather than an actual exit—that's fabricating data to represent a non-exit failure, which feels like a modeling smell similar to manufacturing causes just to populate fields. The fabricated exitCode -1 is the real issue here—it's a synthetic value used to drive the user-facing message, which violates the principle of deriving messages from actual structured attributes rather than manufactured discriminators. It's not a literal rule violation, but it's semantically questionable since the numeric field is doing the work of a discriminator. I'd rate this as a moderate concern worth flagging. The ProcessResourceMonitor mapping is trickier—it's preserving a legacy failure tag that no longer reflects reality since the underlying ps query was removed, so it's mislabeling the error. This isn't clearly flaggable under the stated rules, but it's worth noting as a contract preservation issue. Given the massive truncation of the diff, I need to scan the full file list to identify other service-related modules that might have similar issues. I'm scanning the new service modules for convention violations. The ElectronPowerMonitor module stands out—it's exporting a singleton instance and wrapping it in a Layer, which violates the principle against hiding dependencies in module globals or imperative API calls. But wait, looking closer, this is actually a legitimate platform adapter for Electron's global powerMonitor object, and all the methods return Effects properly. It follows the same pattern as the existing ElectronApp module, which is already approved. The convention allows Layer.succeed for this kind of adapter work, so this isn't a real violation. Now I'm checking the background activity scopes in the mobile and web apps—they're using module-global mutable maps with functions that return Effects, which looks like it might violate the convention against hidden runtime state. The mobile and web modules are separate copies anyway, so the module-global registry pattern mirrors how client code already uses browser globals like document and localStorage throughout — it's consistent with that layer of glue code rather than a hidden async dependency, so I'm leaning toward not flagging it. Now looking at the RpcAuthorization module in the server auth code, which is a pure lookup utility with named imports. channel is unknown or mixed, so Effect.catch works here, though the convention prefers Effect.catchTags for statically known tagged failures — in this case the error is only ProcessSignalFailed, so technically Effect.catchTags would be more aligned with the pattern, but Effect.catch is acceptable when the error type isn't fully constrained. Now looking at ProcessResourceMonitor.ts, I notice it's mapping a native health message into a failureTag: "ProcessDiagnosticsQueryFailedError", but that error class was actually deleted in this PR, so the contract literal no longer corresponds to an existing error definition. Defining the error class... The author did address the binary.resolve issue by propagating the structured error, but the NativeTelemetryUnavailable class itself still follows the problematic pattern—storing an unstructured reason string and deriving its message from other errors' messages rather than from its own structural attributes. This is partly a repeat of the prior feedback, though the earlier comment specifically called out that exact mismatch between the class design and the convention. Given the guidance against duplicate comments and that the author already made a targeted fix in response, I'll skip re-flagging this one. Now looking at NativeTelemetryExited for the decoder-stream-ended path, which is using a fabricated sentinel exit code... Actually, this does warrant flagging. The -1 exit code doesn't reflect reality—it's manufactured to fit the error shape—and produces a misleading message like "Resource monitor exited with code -1" that surfaces in health reporting and the UI. This violates the principle of not fabricating data to populate error structures, and it's a distinct issue on a new file that hasn't been previously flagged. I'll call it out. Let me scan for other clear issues... Looking at the RPC client setup, the EnvironmentRpcSubscriptionObserver is properly modeled as a Context.Reference with a default value, which aligns with the recommended pattern for optional services. The ResourceTelemetry module uses layers correctly, and the ResourceTelemetryRefreshFailed error with its two operation values is fine. The refreshHealth and rebuild operations look sound. Checking ProcessDiagnostics... Handling errors in the telemetry refresh pipeline... The issue is that String(cause) and Cause.pretty(cause) are inserting arbitrary defect text into log annotations and caller-visible RPC fields like lastError, which violates the rule against copying unfiltered error details into those channels. This is a genuine violation in NativeTelemetryClient where the pretty-printed cause with stack traces leaks into the health response. I need to check the actual code in NativeTelemetryClient.ts to confirm the exact behavior and get precise line numbers for my analysis. I'm spotting an issue in NativeTelemetryClient.ts where a fabricated exit code of -1 is being used as a sentinel value to signal that the decoder stream has ended, which then surfaces to callers as "Resource monitor exited with code -1." This should really be its own distinct error type instead—something like NativeTelemetryStreamClosed—since the DesktopTelemetryReceiver already has a proper model for exactly this scenario. There's also a second problem at line 717 where arbitrary defect text (stack traces) is being copied directly into a caller-visible health field through lastError: Option.some(Cause.pretty(cause)). This violates the convention against exposing unstructured error details in fields that get rendered in the diagnostics UI and mapped into legacy error messages. The file already has errorMessage(error) for handling structured errors on the normal path, so there's a clear inconsistency here. Both findings are convention violations worth flagging, and I think they warrant failure status since they're explicit violations of the stated error-modeling practices. Now I need to get the exact line numbers from the diff and draft the inline comments. For line 648, I'm crafting a comment that explains how fabricating exitCode: -1 makes the health report misleading — suggesting instead a distinct error type for stream termination, similar to how the sibling module already models it, so the exit code field always reflects a real status. For line 717, I'm concerned that Cause.pretty dumps arbitrary defect text and stack traces into lastError, which gets surfaced to callers through the health diagnostics. I should suggest keeping the pretty formatting for logs only and using a bounded, normalized message instead. I'll propose an inline suggestion with a fixed message like "Resource monitor supervisor stopped unexpectedly." to keep it self-contained and safe for display.

1. modify_pr — Reviewed the new Effect services in this PR against the service conventions. Service definition, make/layer shape, namespace imports, dependency acquisition, and the ProviderEventLoggers/ProcessDiagnostics/ProcessResourceMonitor restructuring all look consistent, and the previously flagged binary.resolve collapse, fabricated timeout cause, and unused ProcessIdentityChanged union are resolved. Two remaining error-modelling issues in NativeTelemetryClient.ts.


Agent Credits: 398 credits