Skip to content

fix(platform): pin error identity in the host contract - #334

Merged
prisis merged 2 commits into
alphafrom
fix/shard-host-contract
Aug 2, 2026
Merged

fix(platform): pin error identity in the host contract#334
prisis merged 2 commits into
alphafrom
fix/shard-host-contract

Conversation

@prisis

@prisis prisis commented Aug 2, 2026

Copy link
Copy Markdown
Member

Review follow-ups to #332. These were pushed to that branch before it merged, but the merge took the first three commits only, so they did not land.

#332 fixed the behaviour; this fixes the assurance and the naming.

The property was pinned only by doubles that assumed it

cloudflare-host.transaction.test.ts asserts against hand-written doubles that implement the very workerd behaviours being claimed — flatten-on-rethrow and abort-on-rejection. A double cannot fail if the platform differs from the author's model of it, and it says nothing about any other host. ShardHost itself was silent on what happens to a thrown error, so a new platform-<target> could regress it and stay green.

The contract now states it on both runSerialized and transaction, and the conformance suite asserts it: a sentinel carrying code/status must come back by identity, and the host must still serve the next caller afterwards.

Verified under real workerd through @lunora/do's runInDurableObject project — the new case fails against the pre-#332 adapter and passes against the fixed one, which is the assurance the doubles could not give. The in-memory reference host and @lunora/platform-node both already satisfy it unchanged.

Also

  • Drop the explicit type argument on blockConcurrencyWhile. It was discarding the closure's return type and then forcing an as never to recover it; blockConcurrencyWhile<T>(cb: () => Promise<T>): Promise<T> infers fine (@cloudflare/workers-types@5.20260724.1/index.d.ts:675). as const on the discriminant removes both.
  • Attach a failed commit/rollback as cause. When the handler's error takes precedence, a broken storage layer was being discarded entirely — the opposite of the "the platform's own failure is surfaced unchanged" rule stated three lines above it.
  • Rename symbolDeclaredUnreachabledeclaredInHandlerModule. After fix: coded errors survive the shard boundary, plus two codegen losses #332 the predicate returns true for exported declarations, which are reachable; the name had become wrong and most of its docblock existed to apologise for that.
  • Cut the duplicated incident narrative. The same ~90-word story appeared in three places. Kept the platform behaviour that cannot be derived from the code, dropped the past-tense bug story — that belongs in the commit, not in a comment that will outlive it.

Checks

platform 45, platform-cloudflare 31, codegen 1045, do 519 plus 56 under workerd, platform-node 52. api:check clean — no public surface change.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CYvgKochPxnrCCtKB1tXGf

Summary by CodeRabbit

  • Bug Fixes

    • Preserved the exact original error when serialized operations or transactions fail.
    • Maintained host availability for subsequent operations after a failed transaction or serialized callback.
    • Improved handling when both an operation and its storage commit or rollback encounter errors.
  • Tests

    • Added coverage for error preservation, rollback behavior, and continued operation after failures.
  • Documentation

    • Clarified error-handling and recovery behavior for transactions and serialized operations.

Review follow-ups on the shard-boundary fix.

The property the previous commit restored was pinned only by unit doubles that
implement the very workerd behaviour being claimed — they cannot fail if the
platform differs, and they asserted nothing for any other host. `ShardHost` also
said nothing about what happens to a thrown error, so a new
`platform-<target>` could regress it and stay green.

The contract now states it on both `runSerialized` and `transaction`, and the
conformance suite asserts it: a sentinel carrying `code`/`status` must come back
by identity, and the host must still serve the next caller afterwards. Verified
under real workerd via `@lunora/do`'s `runInDurableObject` project — the case
fails against the unfixed adapter and passes against the fixed one, which is the
assurance the doubles could not give. The in-memory reference host and the node
host both already satisfy it.

Also from review:

- Drop the explicit type argument on `blockConcurrencyWhile`, which was
  discarding the closure's return type and forcing an `as never` to recover it.
  `as const` on the discriminant lets it infer.
- Attach a failed commit/rollback as `cause` when the handler's error takes
  precedence, so a broken storage layer cannot vanish behind an application
  error.
- Rename `symbolDeclaredUnreachable` to `declaredInHandlerModule`. After the
  previous commit the predicate returns true for exported declarations, which
  are reachable — the name had become wrong and its docblock existed mostly to
  say so.
- Cut the duplicated incident narrative from the comments, keeping the
  platform behaviour that cannot be derived from the code and dropping the
  past-tense bug story.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CYvgKochPxnrCCtKB1tXGf
@netlify

netlify Bot commented Aug 2, 2026

Copy link
Copy Markdown

Deploy Preview for lunorash ready!

Name Link
🔨 Latest commit a56ddd4
🔍 Latest deploy log https://app.netlify.com/projects/lunorash/deploys/6a6faa346d8d08000823f8c6
😎 Deploy Preview https://deploy-preview-334--lunorash.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Thank you for following the naming conventions! 🙏

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 08d249d0-89d9-48ee-a543-1bc11614473a

📥 Commits

Reviewing files that changed from the base of the PR and between dba487c and a56ddd4.

⛔ Files ignored due to path filters (1)
  • packages/platform-cloudflare/__tests__/cloudflare-host.transaction.test.ts is excluded by !**/__tests__/**, !**/*.test.ts and included by packages/**
📒 Files selected for processing (1)
  • packages/platform-cloudflare/src/cloudflare-host.ts

Walkthrough

The change preserves original closure errors in Cloudflare runSerialized and transaction operations, documents these contracts, and adds conformance coverage. It also renames an unchanged codegen helper.

Changes

Host error preservation

Layer / File(s) Summary
ShardHost error contracts
packages/platform/src/shard-host.ts
The documentation requires exact closure error propagation, rollback behavior, and continued host use after failures.
Cloudflare error handling
packages/platform-cloudflare/src/cloudflare-host.ts
runSerialized rethrows the original closure error. Transaction handling keeps the closure error primary and attaches platform failures as cause when possible.
Error propagation conformance
packages/platform/src/conformance/suite.ts
Conformance tests verify error identity and successful subsequent nested transactions after rejected closures.

Codegen helper rename

Layer / File(s) Summary
Handler-module type detection
packages/codegen/src/discover-functions.ts
The local type-detection helper and its recursive call site use the declaredInHandlerModule name. Behavior is unchanged.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ShardHost
  participant CloudflareHost
  participant blockConcurrencyWhile
  participant Closure
  participant TransactionStorage
  ShardHost->>CloudflareHost: invoke runSerialized or transaction
  CloudflareHost->>blockConcurrencyWhile: execute serialized operation
  blockConcurrencyWhile->>Closure: run callback
  Closure-->>blockConcurrencyWhile: return value or original error
  CloudflareHost->>TransactionStorage: commit or rollback transaction
  CloudflareHost-->>ShardHost: return value or rethrow closure error
Loading

Possibly related PRs

  • anolilab/lunora#332: Modifies the same Cloudflare host error propagation and codegen type-reachability areas.
  • anolilab/lunora#240: Modifies packages/codegen/src/discover-functions.ts function-discovery logic.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: enforcing error identity in the platform host contract.
Description check ✅ Passed The description covers the change, linked issue, tests, reviewer notes, and API impact, although it omits the template checklist and CLA section.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/shard-host-contract

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Thank you for confirming the Contributor License Agreement! 🙏

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/platform-cloudflare/src/cloudflare-host.ts`:
- Around line 198-200: Update the error-handling flow around the thrown value to
wrap the cause check and assignment in a try block, allowing failures when
reading or setting cause on frozen or non-extensible errors. Ensure the
surrounding logic always throws thrown.value so the original closure error is
preserved.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5bf8ca21-d39f-4204-81f9-4f769543563f

📥 Commits

Reviewing files that changed from the base of the PR and between fb05fe7 and dba487c.

⛔ Files ignored due to path filters (1)
  • packages/platform-cloudflare/__tests__/cloudflare-host.transaction.test.ts is excluded by !**/__tests__/**, !**/*.test.ts and included by packages/**
📒 Files selected for processing (4)
  • packages/codegen/src/discover-functions.ts
  • packages/platform-cloudflare/src/cloudflare-host.ts
  • packages/platform/src/conformance/suite.ts
  • packages/platform/src/shard-host.ts

Comment thread packages/platform-cloudflare/src/cloudflare-host.ts Outdated
@codspeed-hq

codspeed-hq Bot commented Aug 2, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 11.75%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

❌ 1 regressed benchmark
✅ 198 untouched benchmarks
⏩ 64 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
1 shard × 1000 rows (single round-trip) 2.7 ms 3.1 ms -11.75%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing fix/shard-host-contract (a56ddd4) with alpha (a4503b5)2

Open in CodSpeed

Footnotes

  1. 64 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on alpha (fb05fe7) during the generation of this report, so a4503b5 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Review catch on the `cause` attachment, and a good one: the line meant to stop a
storage fault vanishing could itself destroy the error it was decorating.

A frozen or sealed `Error` — a module-level `Object.freeze(new Error(…))`
sentinel is a real pattern — rejects a `cause` write with a `TypeError` under
strict mode. That `TypeError` then propagated in place of the error the handler
actually threw, which is precisely the loss this whole wrapper exists to
prevent.

The write is now guarded and best-effort, in a named `attachCause` helper —
which also keeps `transaction` under the cognitive-complexity limit that the
inline nesting broke. `defineProperty` rather than assignment states the intent
(add an own property to this instance) and does not read as an accidental
parameter mutation.

Covered both ways: a platform failure is attached as `cause` when the closure's
error can carry one, and a frozen error still arrives by identity when it
cannot. Verified the second test fails without the guard.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CYvgKochPxnrCCtKB1tXGf
@prisis

prisis commented Aug 2, 2026

Copy link
Copy Markdown
Member Author

Fixed in a56ddd421 — thanks, this was a real one.

I reproduced it before accepting: Object.freeze(new Error(...)) then err.cause = other throws TypeError under strict mode, so the assignment would have propagated in place of the handler's error. That is exactly the loss this wrapper exists to prevent — the line meant to preserve information could destroy it.

Taken with two changes to the proposed diff:

  1. Extracted into a named attachCause helper. The inline nested try pushed transaction to cognitive complexity 18 against a limit of 15 (sonarjs/cognitive-complexity), so the extraction was needed regardless — and it reads better than the nesting.
  2. Object.defineProperty rather than assignment, which states the intent (add an own property to this instance) and does not trip no-param-reassign.

Covered both directions: a platform failure is attached as cause when the error can carry one, and a frozen error still arrives by identity when it cannot. I verified the second test fails without the guard, and re-ran the conformance leg under real workerd (56 passed).

@prisis
prisis merged commit e4f813b into alpha Aug 2, 2026
39 of 41 checks passed
@prisis
prisis deleted the fix/shard-host-contract branch August 2, 2026 20:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant