Repository navigation
Effect Service Conventions: 3 issues found
Three error-convention violations in apps/server/scripts/migrate-dev-db.ts (inline comments posted):
MigrateDevDbDestinationBusyError(lines 73-83):reason: Schema.Stringstores an unstructured message fragment as the error's only variable attribute, and the class carries nocause. Use a bounded literal discriminator pluscause: Schema.optional(Schema.Defect()).ensureNotInUse(lines 159-165):Effect.mapError(() => ...)discards the underlyingSqlError, losing the error chain and stack. Preserve it ascause.runMigrateDevDbverify boundary (lines 382-386): hand-rolled_tagcomparison insidemapErrorto pass a known tagged failure through; useEffect.catchTags({ SqlError: ... })instead.
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.
Files examined in scope: apps/server/scripts/migrate-dev-db.ts, apps/server/scripts/migrate-dev-db.test.ts. Context consulted outside the diff: apps/server/src/persistence/Migrations.ts (migrationManifest), apps/server/src/persistence/NodeSqliteClient.ts (layer options), packages/shared/src/devHome.ts (resolveWorktreeT3Home returns string | undefined, so the undefined guard is correct), plus existing CLI files and error definitions across the repo for house style.
Checked and found compliant:
- Effect modules imported as deep namespaces (
effect/Effect,effect/FileSystem,effect/Path,effect/Schema,effect/unstable/sql/SqlClient);import { Command, Flag } from "effect/unstable/cli"matches the established repo-wide barrel style for the CLI module and was not flagged. - Local modules used through namespaces where they are service modules (
NodeSqliteClient.layer), named imports only for pure helpers (runMigrations,migrationManifest,resolveWorktreeT3Home). - Dependencies acquired from the environment (
yield* FileSystem.FileSystem,yield* Path.Path,yield* SqlClient.SqlClient); no service-instance parameters, noManagedRuntime.make, norunPromise/runPromiseExit, noLayer.succeedhiding runtime-backed APIs.NodeRuntime.runMainis confined to theimport.meta.mainCLI boundary. MigrateDevDbPhaseErroruses a multi-valuephaseliteral with a message derived only from structural attributes and keeps the real failure ascause— acceptable under the operation-discriminator rule, and no payloads/credentials are copied into attributes.- New tests cover real behavior (prune filter, slot collision, shared-home guard), so they are not "tests to prove a mechanical refactor".
🔍 Agent activity — 1 tool call
💭 I'm looking at the import conventions in the diff, specifically checking how the Effect library modules are being imported. The code uses a consolidated named import from "effect/unstable/cli" for Command and Flag, but the convention suggests importing Effect library modules from their subpaths as namespaces instead. For the error handling,
MigrateDevDbPhaseErroruses a multi-value operation discriminator with a generic message derived from the phase, which aligns with the allowed pattern. The use ofSchema.Defect()for the cause is acceptable since the message incorporates both phase and databasePath to structurally identify the failure rather than just stringifying the cause. Now looking atMigrateDevDbDestinationBusyError— it storesreasonas an unstructured string that drives the user-facing message, which violates the convention. The convention says to either split semantically distinct failures into separate error classes or use a Literals discriminator instead of a free-form string. Sincereasonis essentially an unstructured message field that's the only data, I should refactor this to use structured attributes with a discriminator rather than storing an arbitrary string. Also catching thatensureNotInUsediscards the underlying SQL error when mapping to the custom error — the cause should be preserved alongside the structural fields instead of being dropped entirely. Looking at the rest of the code, the imports and dependency acquisition look fine —process.cwd()andNodeOS.homedir()are acceptable, and the module namespace usage is correct. TherunMigrationsimport from the persistence module appears to be crossing a service boundary. For error handling, there's a pattern using_tagstring comparison insidemapErrorto pass through already-structured domain errors, but the convention would prefer usingEffect.catchTagsto handle these statically known tagged failures instead. The test file looks fine since this is new behavior requiring test coverage. I'm seeingSchema.TaggedErrorClassbeing used correctly. The real question is whether this counts as an Effect service PR — it's creating a script that consumes the SqlClient service, so the error handling conventions definitely apply. I should check what the repo's import conventions are for this kind of work. The CLI import style follows the house standard, so that's fine. I need to check the error handling conventions in similar I'm examining how similar scripts handle error patterns, particularly looking at theensureNotInUsefunction which currently drops the underlying SqlError when creating a MigrateDevDbDestinationBusyError. The convention should preserve the original error as acausefield, but the error class doesn't have that field defined—a clear gap in the implementation. For thereasonfield, it's currently just a free-form string, but the pattern should use structured attributes instead. The two call sites are hardcoding English strings like "write-locked" and "wal-held", which should be literal types or separate error classes rather than unstructured messages. I should flag both the missingcausefield and the unstructuredreasonfield in a single comment on the class definition. On themapErrorcall site, the code uses a manual_tagternary to check forSqlError, but the convention prefersEffect.catchTagsfor catching statically known tagged failures. The idiomatic approach would be to usecatchTagsto wrap only the lower-levelSqlErrorwhile passing through domain errors that are already part of the declared error channel. I'll note this as a minor inline comment since the current pattern mirrors the anti-pattern the guidelines warn against. ThewrapPhasepattern itself is fine — it wraps the entire migrate phase with context about which phase failed and the database path, which provides useful information without being overly generic. And for thecause: Schema.Defect()inMigrateDevDbPhaseError, that's allowed since the error has both a tag and structured fields. Looking at dependency acquisition,process.cwd()andNodeOS.homedir()aren't violations — they're justified by the diagnostics context and don't hide dependencies in module globals. The test setup withwithDatabaseproviding layers is fine, and the file layout here is a script rather than a service module, so error declaration order doesn't apply. TheEffect.orElseSucceedthat swallows errors inremoveDatabaseFilesisn't a listed convention violation either. I'm looking at the diff line numbers for the review comments—since this is a new file, the right-side line numbers map directly to the actual file lines. For the first finding, I need to flag lines 73-83 where the class definition hasreasonas an unstructured string, which should instead be constrained to specific literal values like "write-locked" or "wal-held", and add an optionalcausefield for defect information. I'll post this suggestion on lines 76-82. I'm looking at how to handle tagged errors in the migration code—specifically whether to use a manual ternary in mapError or leverage Effect.catchTags with its handler pattern. The question is whether returning an error class instance directly works with catchTags since Schema.TaggedErrorClass extends Error and can be yielded. In Effect v4, errors need to be wrapped TheSqlErrortag is properly defined and used with catchTags elsewhere, which is good. I'm noticing thatEffect.mapErrorin ensureNotInUse only handles SqlError on the error channel, so we're losing the cause information there—that's worth flagging. The other checks like thedetailfield requirement and the empty fields inMigrateDevDbNotInWorktreeErrorandMigrateDevDbSharedHomeErrorlook fine. Looking at test conventions, the file usesit.layerandassertwitherror._tagchecks, which aligns with the pattern. TheMigrateDevDbDestinationBusyErrorwith its reason string is interesting—since the reason gets interpolated into the user-facing message, the convention suggests either splitting this into separate error classes or making reason a literal discriminator with a generic message. I should propose both approaches with diffs and then draft the comments. Writing the first comment... I'm seeing thatSchema.optional(Schema.Defect())follows the established convention for handling causes. I need to post three inline review comments: one on line 77 about the missingcauseparameter in the Schema.String definition, another flagging howmapErrordiscards the cause on lines 159-165, and a third suggestingEffect.catchTagsas a better approach for lines 382-386. For the first comment, I'm noting that storingreasonas a prose fragment without preserving the underlyingSqlErrorviolates the error conventions — I should recommend using a bounded literal for the diagnostic message while keeping an optionalcausefield so the error chain survives. The message text can remain the same through mapping or by keeping the sentence in the getter per variant. Now I'm considering the simplest fix: changingreasonto a literal union of the two actual failure modes ("write-locked" or "wal-held"), adding an optionalcausefield, and introducing a private getter that maps each literal to its full prose text. This keeps the message output unchanged while preserving the error chain and using a discriminator instead of an unstructured string. The concern is whether adding a lookup in the message getter violates the convention against large switch statements, but with only two variants it's borderline — I might just suggest the literal plus optional cause and leave the message handling flexible. Refactoring the error handling to useEffect.catchTagsinstead of manual tag comparison, which makes the code more declarative and ensures it stays exhaustive if new error types are added later.
1. modify_pr — Reviewed the new apps/server/scripts/migrate-dev-db.ts (and its test) against the Effect service/error conventions. Imports, Schema.TaggedErrorClass usage, the phase discriminator, and dependency acquisition (yield* SqlClient.SqlClient, layers provided per-database) all look right. Three error-modeling findings inline: the busy error stores a prose fragment as its only variable data and carries no cause, the wrap at the probe discards the underlying SqlError, and the verify boundary hand-rolls a _tag comparison instead of Effect.catchTags.
Agent Credits: 393 credits