Repository navigation
Effect Service Conventions: 1 issue found
1 finding
packages/client-runtime/src/state/runtime.ts(513-521): the new unavailable-query failure collapsesavailable(manual disconnect),offline, andblockedinto one genericEnvironmentRpcUnavailableErrormessage, discarding the knownconnectionState.phaseand the structuredconnectionState.lastFailure(ConnectionBlockedError/ConnectionTransientError). Conventions require capturing stable domain context (phase/category) as structural attributes, preserving the immediate underlying error ascause, and derivingmessagefrom those attributes.
An inline review comment with a suggested shape for the error class and call site has 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 reviewed: packages/client-runtime/src/state/runtime.ts and packages/client-runtime/src/state/runtime.test.ts (the only in-scope changed files), plus supporting context in packages/client-runtime/src/rpc/client.ts, connection/registry.ts, and connection/model.ts.
Checked and found conforming:
- Imports: all Effect modules are imported as namespaces from subpaths (
effect/Effect,effect/Layer,effect/SubscriptionRef, ...); no consolidated named imports from"effect". New test code imports local service modules as namespaces (import * as EnvironmentRegistry from "../connection/registry.ts",import * as EnvironmentSupervisor from "../connection/supervisor.ts") and uses the module shape (EnvironmentRegistry.EnvironmentRegistry). The pre-existing named tag imports inruntime.tswere not touched by this PR and were not flagged as unrelated legacy style. - Service interfaces are referenced as
Foo["Service"](EnvironmentSupervisor.EnvironmentSupervisor["Service"],EnvironmentRegistry.EnvironmentRegistry["Service"]["run"]). - Dependency acquisition: the new atom family still resolves
EnvironmentRegistryfrom the Effect environment (EnvironmentRegistry.pipe(Effect.map(...))); no service instances are injected into production constructors.Layer.succeedand explicit supervisor/registry instances appear only in the test harness (a legitimate test seam), and noManagedRuntime.make/runPromise/runPromiseExitwas introduced. - Tests: this PR changes runtime behavior (interrupted-query retry, unavailable settling, stale-value retention), so the added focused tests are appropriate rather than tests added solely to prove a mechanical refactor.
Only the error-modeling issue above is reported.
🔍 Agent activity — 10 tool calls
💭 I'm reviewing the changed files, looking specifically at how runtime.ts updates connectionStateAtom to use EnvironmentRegistry.stateChanges and switches on phase, plus how the EnvironmentRpcUnavailableError is constructed.
1. browse_code — packages/client-runtime/src/state/runtime.ts
2. git_tools — EnvironmentRpcUnavailableError
3. browse_code — packages/client-runtime/src/rpc/client.ts
4. browse_code — packages/client-runtime/src/state/runtime.ts
💭 I should check whether stateChanges is actually defined in the registry, since it appears in the diff's usage. Let me look at registry.ts.
5. git_tools — stateChanges
💭 I'm checking the import conventions in runtime.ts—it uses a named import from the registry module rather than a namespace import, which conflicts with the service boundary convention. Since this import line itself wasn't changed in the diff, I need to think about whether this counts as a violation retained within the PR's changed scope, given the usage of EnvironmentRegistry was touched. The test file, on the other hand, correctly uses namespace imports and follows the pattern for accessing the service tag. The double cast in the test to create a partial fake service is a bit loose but not explicitly against any listed convention, and adding tests here is justified since this represents an actual behavior change rather than a mechanical refactor. Now looking at the new error being raised for a disconnected environment, I'm checking whether storing a plain message string violates the guidance to derive messages from structured attributes rather than storing unstructured text. Since the error class's message field is pre-existing and the new call site just follows that established pattern, and environmentId is also captured as structural context, this seems consistent -- though I notice the message collapses the available/offline/blocked distinction into a single string rather than capturing the phase as a discrete field. Also considering that the message field is stored rather than derived, though that's part of the pre-existing class so out of scope for this change. I check other minor things too: the Effect.never usage in the atom is just behavior, the test's dependency layer setup is a legitimate seam, and imports like EnvironmentSupervisor are still actually used elsewhere in the file. Now I want to verify the switch statement covers every phase in the SupervisorConnectionState union exhaustively, so I need to check the model definition for that type.
6. git_tools — phase
💭 I'm looking for concrete violations to flag rather than assuming failure by default. One candidate is the error message construction that duplicates generic text while dropping phase information, which conflicts with the convention of capturing stable, serializable context like the operation or stage whenever it's known. I'm also weighing whether collapsing three distinct states—manual disconnect, offline, and blocked—into one identical error message counts as a real violation, given there's precedent in client.ts for similar generic messaging, and given instructions not to flag optional style preferences. On the import style question, I notice runtime.ts uses named imports for EnvironmentRegistry and EnvironmentSupervisor even though these are service-boundary consumptions that convention suggests should be namespace imports—but since these imports are pre-existing and used throughout the file, flagging them would require an unrelated file-wide refactor, so I'm skipping that. I also notice the test file uses namespace imports for the same modules, creating inconsistency, but again this predates the PR. Looking at the test's fake registry cast and TestQueryError class, neither seems to violate conventions meaningfully—the double cast isn't explicitly disallowed, and the tagged error with an unstructured message is just a test fixture, not a real service failure, so I wouldn't flag it either. The only real candidate so far is the earlier error context/message issue, but given how the framing of this task suggests findings are expected, I want to look more carefully at runtime.ts for anything I might have missed. Now I'm considering whether phase-based control flow duplicates existing error semantics, and I want to see how downstream consumers actually handle these errors—do they catch things like EnvironmentRpcUnavailableError and surface a message anywhere? I should check vcs.test.ts and sourceControl.test.ts for usage patterns and any catchTags wrapping these errors.
7. git_tools — EnvironmentRpcUnavailable
8. git_tools — packages/client-runtime/src/state/runtime.ts
💭 I'm checking whether the new error construction in the changed scope actually violates the convention, since the message is derived from environmentId but loses phase context — trying to determine if that counts as a genuine violation worth flagging. I think the strongest candidate is that the new failure path merges three distinct connection phases into one generic message and drops the known phase/lastFailure context, which does look like a real violation of capturing stable domain context. I'm ruling out other candidates: the registry pipe usage, the Effect.never backoff behavior, and the test harness's unsafe cast all seem acceptable per convention, and the duplicated test harness isn't a convention issue either. Now I want to confirm how stateChanges behaves when the environment isn't registered. I'm weighing whether collapsing three distinct connection phases into one identical error message is a genuine convention violation or just a minor smell—probably worth flagging as a finding, but I'm unsure if it warrants a failure state versus something more neutral given the checked repo's stated conventions around structured error messages derived from tagged error attributes. I'll draft an inline comment on the relevant lines suggesting the phase be captured as a structured attribute with the message derived via a getter, rather than storing the unstructured message directly—that seems like the most concrete, convention-anchored suggestion I can make here. Since this is a cross-file error class, I don't have a diff to attach, just a description. Let me also quickly check the test file's imports to see if there's a clearer, more clear-cut issue worth flagging instead.
9. browse_code — packages/client-runtime/src/state/runtime.test.ts
💭 The test file follows convention. I'm weighing whether the new error construction loses the phase context — that seems worth flagging as a comment, since the class likely needs a phase field added. This isn't unrelated legacy code; it's a new failure site, and the convention calls for capturing state at the point of failure. Here the strongest issue is that in the blocked/offline cases,
connectionState.lastFailureholds a structured error (ConnectionBlockedError or ConnectionTransientError) that gets discarded in favor of a generic message, meaning users can't tell "offline" apart from "access denied." This directly violates the convention that real underlying failures should be preserved as the cause when wrapping errors. I'm deciding to post an inline comment on the runtime.ts call site, describing the fix — carrying the underlying error ascauseand capturing phase/category — rather than trying to draft a full diff spanning multiple files. Given this is a concrete, non-stylistic modeling issue rather than a legacy or cosmetic concern, I'm leaning toward marking this as a failure, since the check defaults to failure and I have a genuine finding here. I'm pinning down the exact lines for the inline comment: start at line 513 where the case statement begins, through line 521 where the fail call closes, anchoring on line 516 where the actualEffect.failcall sits.
10. modify_pr — One finding on the new unavailable-query failure path in packages/client-runtime/src/state/runtime.ts. Everything else (namespace imports, Foo["Service"] usage, test-only Layer.succeed/service instances) looks consistent with the Effect service conventions.
Agent Credits: 73 credits