Skip to content

refactor(effect): yieldable framework error classes via Data.Error bases - #543

Merged
ScriptedAlchemy merged 5 commits into
mainfrom
refactor/data-error-classes
Sep 4, 2026
Merged

refactor(effect): yieldable framework error classes via Data.Error bases#543
ScriptedAlchemy merged 5 commits into
mainfrom
refactor/data-error-classes

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Summary

Maintainer decision (2026-09-03): adopt Data.Error for framework-process error classes; Schema.TaggedError stays deferred. This PR adds packages/agent-bundle/src/effect/errors.tsYieldableFrameworkError (the Data.Error twin of Error, same (message?, options?) constructor) and YieldableCodedError<TCode> (the twin of CodedError, same (name, code, message, options?) constructor and code field) — and swaps the extends clause on the 14 internal dev-seam / eval classes that no public declaration graph reaches (DevCoordinatorCloseError, RuntimeMcpRegistryError / RuntimeMcpRegistryCloseError, RuntimeGenerationStoreError / RuntimeGenerationStoreCloseError, DevRuntimeProviderLoadError, ScriptPlaygroundFailure / ScriptPlaygroundAbortError, LifecycleReplayRequestError, ArtifactInspectionServiceError, HookSimulationAbortError / HookSimulationTerminationError, CodexEvalHarnessError, SmokeStepError) so programs can write return yield* new X(...); the coordinator's close failure is converted as the demonstration site. No constructor signature, message, or code changes; no other call-site churn (Effect.fail(new X(...)) still works).

The bases keep the plain-Error observable shape rc.112 Data.Error would otherwise change: its prototype toJSON spreads message/cause into JSON (and stableJson would then sort keys), its [nodejs.util.inspect.custom] prints that dump instead of the stack, and a falsy cause becomes an enumerable own field. The base shadows both prototype members with non-functions (every consumer checks typeof === 'function' first) and installs cause exactly like new Error(message, { cause }). tests/effect-errors.test.ts pins byte-identical JSON.stringify / stableJson / spread / structuredClone output against the plain twin, util.inspect stack rendering, instanceof, .name / .code / .message / .cause / .stack, yield* through runPromise / runPromiseExit, and isTypedDevError.

Carve-outs (stay on plain Error / CodedError)

  • Effect-free entry graphs — measured with a dist chunk-graph walk: putting CodedError on Data.Error adds a static effect import to agent-bundle/config (CapabilityStateError), agent-bundle/meta + agent-bundle/rstest (MetaUnavailableError), the host MCP proxy (DevLockError), and the CLI --help/--version path (cli.test.ts module-load proof from perf(cli): load the Effect terminal runtime lazily to restore cold-start time #530). So CodedError, DiagnosticError, DevLockError stay plain; McpAppBridgeCloseError too (agent-bundle/rstest and test/browser reach it). After this PR the set of Effect-bearing dist entries is identical to main.
  • The public declaration graph (found by the Node 24 public-api.test.ts root-declaration consumer failing on the first push): a consumer's tsc resolves every .d.ts an export reaches, so a class extending the yieldable base in any reachable declaration makes effect a type dependency of the package — exported or not. With the first migration set, dist/dev/mcp-session/mcp-session-types.d.ts (and 16 more dev-seam declaration files) were reached from ., ./api, and ./eval. Those 31 classes are back on plain Error / CodedError (McpSessionError + siblings, EpochStoreError + epoch cleanup/durability errors, ProjectEventHubError, HostMcpEpochDriftError, AgentApiCloseError, DevLogServiceError, DevServerStartError / DevServerLifecycleCloseError, ForegroundServer*Error, DevRuntimeUnavailableError / DevRuntimeGenerationConflictError, PlaygroundService*Error / PlaygroundSessionCloseError, HookPlaygroundCloseError, McpProbeTargetNotFoundError, McpAppRuntimePreviewError, SkillDocumentError, InspectorLauncherError, EvalRunEvent*Error, EvalServiceBackgroundFailureOverflowError), together with everything exported from an entry: Agent* / McpProjectionError (@agent-bundle/runtime; the plugin entry has no effect import today), CliUsageError / CliInputError, EventRuntimeTransportError, Eval*Error on agent-bundle/eval, EvalServiceError, AgentTestError, BrowserAppTestError, create-agent-bundle's UsageError. This is the reason rsc-runtime and create-agent-bundle have no code change here. New guard: public-api.test.ts "keeps every public declaration graph free of effect" BFS-walks each package.json export's emitted .d.ts graph and fails on any effect import (negative check: an injected import in dist/dev/agent-api.d.ts is reported for . and ./api; one in the unreachable coordinator.d.ts is not). A class is eligible for the yieldable base only while that test stays green with it migrated.
  • Emitted artifacts: hook wrappers / bins / framework MCP shell bundle src/effect/boundary.ts (plain CodedError); the raw stdio server and install.mjs have no Effect.
  • Workbench client errors (browser; effect only in atoms).

Emitted artifact measurement (examples/host-test, source-built CLI, before → after)

All 66 emitted files are byte-identical in size; the hook wrapper is byte-identical in content. Per class (claude target):

Artifact class Before After
hook wrapper hooks/event-route-stop.mjs 2 387 800 2 387 800
hook Flight worker hooks/hooks-flight.mjs 3 157 120 3 157 120
CLI bin bin/host-test.mjs 2 903 915 2 903 915
CLI bin Flight worker bin/host-test-flight.mjs 3 145 841 3 145 841
MCP framework shell mcp/mcp-host-test-*.mjs 4 091 738 4 091 738
MCP Flight worker mcp/mcp-host-test-*-flight.mjs 3 169 475 3 169 475
raw stdio MCP server mcp/mcp-host-test-raw-*.mjs (no Effect) 1 222 685 1 222 685
install.mjs (cursor/portable, no Effect) 64 603 64 603

Why no growth: Data.Error is core.Error inside Effect's internal/core.ts, which every Effect-bearing artifact already bundles (effect/Data/Error/plainArgs was present before). The +12 kB figure in docs/effect-conventions.md was effect/PlatformError's Schema.TaggedError, not Data.Error. The docs row is corrected accordingly.

Durable test: tests/emitted-artifact-effect-surface.test.ts (integration lane, builds examples/host-test in-process from the built dist) asserts every artifact class is emitted, no emitted file contains the yieldable base, the Effect-free classes stay Effect-free (effect/Data/Error/plainArgs absent), and hook wrappers keep class CodedError extends Error. A byte-size gate with slack cannot see a 12 kB delta in a 2.4 MB unminified wrapper, so the invariant is pinned by content; negative check done locally (making EventRuntimeTransportError yieldable fails all hook wrappers after rslib build).

Docs

  • docs/effect-conventions.md: new "Yieldable framework errors" section (rule, what is unchanged, carve-outs); "Open decisions" row recorded as decided; boundary-module line updated.
  • agent-patterns/effect-errors.md: yield* pattern + declaring a framework-process error.
  • No website/ change: no public page describes these internal classes; pnpm docs:site:build green.

Verification

pnpm typecheck, pnpm lint, pnpm test:unit (3261 passed), pnpm test:projection (158 passed), cli.test.ts (Effect module-load proof, 16 passed), emitted-artifact-effect-surface.test.ts, effect-errors.test.ts, pnpm bench:hook-cold-start -- --check (ok), pnpm docs:site:build (parity ok). Packed lane: see comments.

Pattern note for the open lanes (fix/route-skill-bugs, fix/runtime-install-gaps, feat/457-lineage-tree, feat/458-lineage-notices, feat/466-canonical-event-payload, feat/369-mcp-tasks, feat/effect-filesystem-phase2)

On rebase, an internal error class you add or touch in the dev seam / eval service extends YieldableFrameworkError (extends Error → same constructor) or YieldableCodedError<Code> (extends CodedError<Code> → same constructor) from src/effect/errors.ts, and programs may return yield* new X(...). Keep plain Error / CodedError if the class's declaration file is reachable from any package.json export (run pnpm build and public-api.test.ts — "keeps every public declaration graph free of effect" names the offending .d.ts; most dev-seam service errors are reachable through the dev types and stay plain), is reachable from agent-bundle/config|meta|rstest|test/browser, the CLI --help path, or the host MCP proxy, or ships in an emitted artifact — public-api.test.ts, cli.test.ts, and emitted-artifact-effect-surface.test.ts fail if crossed. Never extend Data.Error directly (the bases restore plain-Error toJSON/inspect/cause). Merge conflicts are one extends line + one import per file.

Review status

  • 43ec5af: reviewed by chatgpt-codex-connector (one P1 on changeset prose — rewritten in d84e475, thread answered).
  • d84e475 (current head): re-review requested.

…dable via Data.Error bases

Add src/effect/errors.ts with YieldableFrameworkError / YieldableCodedError
(rc.112 Data.Error twins of Error / CodedError, same constructors, plain
Error toJSON / util.inspect / non-enumerable cause restored) and swap the
extends clause on the internal dev seam and eval classes. CodedError,
DiagnosticError, DevLockError, McpAppBridgeCloseError, every class exported
from a package entry, and emitted artifacts stay plain (measured: 66
host-test artifacts byte-identical in size; Effect-free entries unchanged).
Record the maintainer decision in docs/effect-conventions.md and
agent-patterns/effect-errors.md; pin serialization and the artifact
surface with tests.
@changeset-bot

changeset-bot Bot commented Sep 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d84e475

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
agent-bundle Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 4, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-04T08:15:23.203716Z d84e475 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@pkg-pr-new

pkg-pr-new Bot commented Sep 4, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle@543
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@543
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/@agent-bundle/runtime@543

commit: d84e475

@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 43ec5afc3f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .changeset/data-error-yieldable-framework-errors.md Outdated
…per export

A consumer's tsc follows every .d.ts a package export reaches, so a class
extending the yieldable base in a reachable declaration made effect a type
dependency (public-api root-declaration consumer failed on Node 24). Move
the 31 dev-seam classes on the root / api / eval declaration graphs back to
plain Error / CodedError; keep the 14 classes no export reaches on
YieldableFrameworkError / YieldableCodedError. Add a public-api test that
walks each export's emitted declaration graph and fails on any effect
import, convert the coordinator's close failure to yield*, and record the
declaration-graph carve-out in docs/effect-conventions.md,
agent-patterns/effect-errors.md, and the changeset.
@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Another round soon, please!

Reviewed commit: d84e475525

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant