Skip to content

Merge origin/main into t3code/ssh-connect-setup

MacroscopeApp / Macroscope - Effect Service Conventions failed Jul 18, 2026 in 3m 40s

Effect Service Conventions: 4 issues found

Found 4 Effect service convention violations in the changed scope:

  • apps/server/src/cli/connect.ts:93 — Effect.catchTag("CloudCliCredentialRefreshError", ...) should be Effect.catchTags({ ... }); the convention requires catchTags even for a single tag.
  • apps/server/src/cli/connect.ts:390 — Effect.catchTag("BootServiceUnsupportedError", ...) should be Effect.catchTags({ ... }).
  • apps/server/src/cloud/CliTokenManager.ts:225 — Effect.catchTag("TimeoutError", ...) should be Effect.catchTags({ ... }).
  • apps/server/src/cloud/bootService.ts:121-131 (constructed at runStep, lines 221-230) — BootServiceCommandError discards the underlying failure, copies cause.message and raw command stderr/stdout into a detail field, and derives message from detail. It should preserve the underlying error as cause, derive message from stable structural attributes (step), and drop the copied detail.

Inline review comments with suggested fixes have been posted.

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: changed TypeScript in apps/** and packages/**.

Methodology: reviewed the new/modified Effect service code against the customer's conventions. Verified the catchTag findings are genuine deviations by confirming the codebase overwhelmingly uses Effect.catchTags (single-key catchTags is idiomatic here, e.g. apps/server/src/provider/copilotRuntime.ts:425), and confirmed CliTokenManager.ts:333's catchTag is pre-existing (commit 4ee719a, out of scope) and therefore not flagged.

Items checked and found acceptable: service definition structure of bootService.ts (Context.Service tag with inline interface, real make, layer via Layer.effect(...).pipe(Layer.provide(...))); BootServiceInstallError using Schema.Defect() for cause with a static message (allowed — no additional variable context, message does not stringify cause); dependency acquisition via yield* Foo.Foo and the Context.make/Effect.provide(services) instance-capture pattern in CliTokenManager.make (legitimate, not fake DI); namespace import of the local service module import * as BootService from "../cloud/bootService.ts"; and the local Schema.is predicate isCloudCliTokenManagerError (used directly, not wrapped by a redundant function).


🔍 Agent activity — 1 tool call

💭 I'm examining the diff to identify any Effect service convention violations, starting with the new and modified files like the CLI connect module, CliTokenManager, and the new bootService to see if they follow the established patterns. Looking at how errors are handled in connect.ts, there's a private Schema.is constant for the CliTokenManager error that's used directly without being wrapped in a redundant function, which actually aligns with the conventions. Now I need to check the BootServiceCommandError structure and how its detail field is constructed... I'm seeing that the detail field is being populated with raw command output—stderr or stdout—which directly violates the convention against copying command output into error details. There's also a separate issue where cause.message is being copied directly into detail, which the convention explicitly warns against, especially when that message is then used to construct the wrapper's message getter. The bigger problem is that BootServiceCommandError doesn't preserve the underlying cause at all—it only stores step and detail—so the actual error chain is lost. This is a clear violation of the requirement to preserve the immediate underlying error as cause when wrapping failures. Now looking at BootServiceInstallError, which defines cause as a Defect and has a static message. This follows the convention properly since it doesn't copy cause.message into detail and preserves the cause itself. The error could arguably retain filesystem context like paths and directories from the wrapping site, but that's a softer concern. Let me examine how Effect.catch is being used in connect.ts to see if there are issues with error handling there. I'm looking at the uninstall pipe chain and noticing it uses Effect.catchTag for a single error tag, but the convention says to use Effect.catchTags with an object even when handling just one tag, and to avoid catchTag altogether. This is a violation that should be corrected. I'm seeing another violation where catchTag is used for a single error type when catchTags should be used instead. But there's also a correct usage of catchTags with multiple error types that handles the remaining errors properly. Let me continue checking the other instances. with Effect.catchTags({ ... }) instead. So I've found three violations across the codebase: one in CliTokenManager.ts handling TimeoutError, and two in connect.ts handling BootServiceUnsupportedError and CloudCliCredentialRefreshError. Now I'm checking the import conventions to see if there are any issues there. The convention allows named imports of service tags from package subpaths, so using { HostProcessEnvironment } directly isn't a violation—it's just a less consistent choice compared to namespace imports. Looking at how it's used in bootService.ts with the same pattern, I'm reconsidering whether this is actually worth flagging at all. Now I'm checking the service definition structure in bootService.ts to see if the ordering follows the canonical pattern: imports first, then error and schema declarations, the Context.Service tag, followed by make and layer. The helper functions are interspersed in the middle rather than grouped at the end. The main violations I'm seeing are the three catchTag usages, which clearly go against the convention. I'm also looking more closely at BootServiceCommandError to understand how it's handling the error cause and whether it's properly preserving the original error information when constructing the detail field. The real issue is that the error isn't preserving the underlying cause and is dumping raw output into the detail field instead of using a normalized category. Adding a cause field would maintain the error chain while keeping detail focused on meaningful context rather than raw command output. I'm reconsidering whether this is actually a problem for a local CLI tool. The log file intentionally writes the full error message including stderr for diagnostics, which is reasonable for a service that users read locally. The concern about raw command output leaking into structured error fields is more about sensitive data exposure, but for a boot service's diagnostic logs, that's arguably acceptable. The clearer issues are the three instances of catchTag that should be catchTags per the convention, and the BootServiceCommandError pattern where cause.message gets copied into detail without preserving the underlying cause chain. Let me verify the actual line numbers in the diff so I can provide specific inline comments for the review. I need to adjust the repo parameter to be an object with a url property. I'm looking at the error handling declarations for BootServiceCommandError, disconnectCloud, and CliTokenManager to understand their catch tag implementations. Looking for the CliTokenManager catchTag line. I'm checking whether the codebase already uses catchTag to understand the existing patterns, though the convention would take precedence regardless of what I find in the older code. The codebase heavily favors catchTags with dozens of instances compared to just a few catchTag usages, making it clear that catchTags is the standard convention. The PR introduces new catchTag violations that should be flagged, though the existing legacy instances in files like http.ts and ProjectionPipeline.ts fall outside the scope of this review. I can see three new catchTag usages that are definitely in the diff: two in connect.ts at lines 93 and 390, and one in CliTokenManager.ts at line 225 within the new pasteCodeLogin function. There's also a TimeoutError catchTag at line 333 in CliTokenManager.ts that I need to verify—it might be pre-existing code rather than a new addition, so I should check the git blame to confirm whether it's actually part of this PR's changes. Line 333 is from an older commit, so it's outside the scope of what I should flag—the convention only covers violations introduced or retained in the pull request's changed scope. That leaves three catchTag violations to consider: connect.ts at lines 93 and 390, plus CliTokenManager.ts at line 225. Now I need to verify whether flagging catchTag violations actually aligns with the convention. The convention is clear: use Effect.catchTags({ ... }) for statically known tagged failures, even when handling just one tag, and avoid catchTag altogether. Since the codebase consistently follows this pattern, these three instances are genuine violations worth flagging. For the BootServiceCommandError issue, the runStep mapError is doing exactly what the convention forbids—it's copying cause.message into a detail field and then using that detail to construct the wrapper message. That's the explicitly-named anti-pattern. Additionally, BootServiceCommandError doesn't preserve the underlying ProcessRunner error as a cause field; it just discards it, which violates the requirement to keep the immediate underlying error alongside structural fields. The filterOrFail branch compounds this by putting raw command stderr and stdout directly into the detail field, which the convention explicitly warns against. The strongest part of this finding is that the error drops the underlying cause entirely while copying cause.message into detail—that's the core violation I should focus on in the comment. For a local CLI tool, exposing systemctl or npm stderr in error messages is less of a security concern since it's diagnostic output intentionally logged to a file, but the pattern of losing the actual cause object while extracting just its message string is the real problem. I have concrete violations here, so I'll fail the check and recommend adding a cause field to preserve the underlying error while deriving the message from structured attributes instead. Now checking the isCloudCliTokenManagerError predicate at connect.ts — it's a module-private constant wrapping Schema.is, used directly without a redundant function layer. The convention flags private constants wrapped by redundant functions, not direct usage like this, so it's not a violation. It's a local predicate and doesn't need exporting. Moving on to the store method on CloudCliTokenManager and how dependencies are acquired in CliTokenManager.make. Building the services context... I'm identifying three places where Effect.catchTag should be replaced with Effect.catchTags for better error handling — in connect.ts for CloudCliCredentialRefreshError and BootServiceUnsupportedError, and in CliTokenManager.ts for TimeoutError. Let me check the bootService implementation around line 121-131... Confirming that single-key catchTags is idiomatic in the codebase, so the refactoring is sound. Now I'm converting the catchTag call in connect.ts to use catchTags instead, wrapping the error handler in an object with the error type as the key. Finishing the TimeoutError case conversion and then moving on to document the BootServiceCommandError fix, which requires changes across multiple locations including adding a cause field and updating the error construction sites. I'm double-checking the BootServiceCommandError severity to make sure this is a real issue, not a false positive. The convention explicitly forbids copying cause.message into a detail field and then using that detail in the message — and line 222 does exactly that with detail: String(cause.message). Plus there's no cause field preserved at all, which violates the requirement to keep the underlying error. For the filterOrFail branch, the detail contains raw command output (stderr or stdout), which also matches the convention against copying raw output into detail. The difference is that branch doesn't have an underlying Error object since it's just a non-zero exit code, but the principle still applies. Now I'm ready to write this up. I'll mark the check as failed and construct the inline comments with careful attention to indentation — the first suggestion goes inside the .pipe() call at 4 spaces, replacing the original Effect.catchTag pattern with a Effect.catchTags object that handles the error tag more cleanly. For the second location at line 390, I'm converting a single-line catchTag call to the new catchTags format. Then for the third file, I'm continuing the same refactoring pattern where the timeout error handler gets restructured. Now I need to prepare the GitHub review with all these comments, making sure each one has the required fields for the API call. The API will reject start_line == line, so I need a different approach. For single-line comments, I could expand the range to include surrounding context like lines 386-390, but then the suggestion would need to cover all those lines, which doesn't match what I'm actually replacing. Given the constraints, I'll need to figure out what the tool actually supports for single-line suggestions. Let me draft the comment bodies concisely: the first two comments will explain that catchTags should be used instead of catchTag for handling tagged failures, even for single tags, since that's the convention throughout the codebase. I'll add suggestions to both. The third comment is for the CliTokenManager around lines 225-227. For the fourth comment on BootServiceCommandError, the issue is that it's discarding the underlying cause and instead storing the cause's message as a string in the detail field, then deriving the message from that detail. According to error conventions, I should preserve the actual underlying error as the cause and keep the message derived from stable structural attributes like step and exit code, rather than copying raw command output into detail. Now I need to finalize the comment with the correct line range (121-131 for the class definition), then post the review via the GitHub tool and generate the final JSON output for the check run.

1. modify_pr — Effect service conventions review found four issues: three uses of Effect.catchTag that should be Effect.catchTags, and a BootServiceCommandError that discards its underlying cause and derives its message from copied cause.message/raw command output. See inline comments.

💭 Now I'm generating the final JSON output for the check-run with a failure status.


Agent Credits: 131 credits