Skip to content

fix(rivetkit): bind methods through createWriteThroughProxy#4987

Open
abcxff wants to merge 1 commit into
05-14-fix_rivetkit_revert_remove_client-side_connection_request_size_checkfrom
05-06-fix_rivetkit_bind_methods_through_createwritethroughproxy
Open

fix(rivetkit): bind methods through createWriteThroughProxy#4987
abcxff wants to merge 1 commit into
05-14-fix_rivetkit_revert_remove_client-side_connection_request_size_checkfrom
05-06-fix_rivetkit_bind_methods_through_createwritethroughproxy

Conversation

@abcxff
Copy link
Copy Markdown
Contributor

@abcxff abcxff commented May 6, 2026

Fixes an error that occurs when inspector state attempts to serialize non trivial values (ie Date).

@railway-app
Copy link
Copy Markdown

railway-app Bot commented May 6, 2026

🚅 Deployed to the rivet-pr-4987 environment in rivet-frontend

Service Status Web Updated (UTC)
kitchen-sink ❌ Build Failed (View Logs) Web May 14, 2026 at 6:09 pm
website 😴 Sleeping (View Logs) Web May 13, 2026 at 8:32 pm
frontend-cloud ❌ Build Failed (View Logs) Web May 13, 2026 at 8:18 pm
ladle ❌ Build Failed (View Logs) Web May 13, 2026 at 8:18 pm
frontend-inspector ❌ Build Failed (View Logs) Web May 13, 2026 at 8:17 pm
mcp-hub ✅ Success (View Logs) Web May 6, 2026 at 8:30 pm

Copy link
Copy Markdown
Contributor Author

abcxff commented May 6, 2026

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 6, 2026

PR Review: fix(rivetkit): bind methods through createWriteThroughProxy

Overview

This PR fixes actor state not being tracked when methods are called on complex objects like Date, Map, Set, and typed arrays (e.g. state.date.setFullYear(2030) silently failing to persist). The root cause was the old hand-rolled Proxy-based createWriteThroughProxy only intercepting property set/deleteProperty traps — it never saw mutations via method calls. The fix replaces it with @rivetkit/on-change, which wraps those methods internally.

The PR also:

  • Introduces JsonCompatValue type and assertJsonCompatValue to validate state at write-time (replacing the boolean-returning isCborSerializable predicate removed from utils.ts).
  • Adds explicit Set support to encodeJsonCompatValue/reviveJsonCompatValue (new $Set tag).
  • Strengthens encodeJsonCompatValue to throw TypeError on unserializable values rather than silently passing them through.
  • Adds actor field to AsyncAPI inspector schemas (appears unrelated to the core fix — worth calling out in the PR description).

Issues

Missing NaN/Infinity validation

The old isCborSerializable explicitly checked !Number.isFinite(value) and returned false. assertJsonCompatValue accepts all typeof value === "number" values including NaN and Infinity, which are not valid JSON or CBOR. These should either be rejected or have a test asserting the intended behavior.

Map key is not validated through the proxy

onValidate in @rivetkit/on-change is called with the new property value (second argument), not the Map key. So state.map.set(nonSerializableKey, "v") will bypass assertJsonCompatValue — the key is never checked. Add a test and either validate keys separately or document the gap.

encodeJsonCompatValue is a silent breaking change

The old function ended with return input — class instances would pass through. Now any non-whitelisted object throws TypeError. If any existing actor state contains class instances, they'll fail at runtime with no migration path. Worth noting in the PR description or changelogs.

No cleanup / unsubscribe for the on-change proxy

onChange() returns a proxy. The on-change library exposes onChange.unsubscribe(proxy) to stop tracking. The current code never calls unsubscribe. Verify that @rivetkit/on-change does not hold strong references to the proxied object, and add a comment if confirmed safe.

onValidate always returning true is subtle

onValidate(_path: string, newValue: unknown) {
    beforeChange?.(newValue);
    return true;
},

This is fine but an unchecked throw from beforeChange for a non-validation reason would also silently prevent mutation. A one-line comment clarifying that rejection is always throw-based would help future readers.


Suggestions

Test coverage gap: Map/Set method mutations through onValidate

There is no test for map.set("k", () => {}) or set.add(() => {}) through the proxy with a beforeChange that calls assertJsonCompatValue. The existing tests only verify direct property assignment. Consider adding:

test("beforeChange rejects non-serializable Map value", () => {
    const state = createWriteThroughProxy(
        { m: new Map<string, unknown>() },
        vi.fn(),
        (v) => assertJsonCompatValue(v),
    );
    expect(() => state.m.set("k", () => {})).toThrow(TypeError);
    expect(state.m.has("k")).toBe(false);
});

Dropped ws peer dep for openai

In the lockfile, openai@6.26.0(zod@4.3.6) loses the ws optional dep. Verify this doesn't affect WebSocket-based streaming in any pi agent path.

@ungap/structured-clone deprecation

The lockfile notes a CWE-502 deprecation on @ungap/structured-clone@1.3.0 — worth bumping to >=1.3.1 to clear the warning.


Strengths

  • The core fix is correct and well-motivated. Replacing the hand-rolled proxy with a dedicated library that properly intercepts method calls on Date/Map/Set/TypedArray is the right call.
  • assertJsonCompatValue is a significant improvement over the old predicate — fail-by-default at write time matches the CLAUDE.md "Fail-By-Default Runtime" constraint.
  • Test coverage for the proxy and encoding round-trips is thorough.
  • Extracting createWriteThroughProxy into its own file improves maintainability.
  • The Set encode/revive round-trip (via $Set tag) is a genuine correctness fix — previously Sets were silently lost on serialization.

@abcxff abcxff force-pushed the 05-06-fix_log_error_on_failed_inspector_requests branch from b150344 to fc4756e Compare May 6, 2026 22:03
@abcxff abcxff force-pushed the 05-06-fix_rivetkit_bind_methods_through_createwritethroughproxy branch from af6e0e4 to 3be897d Compare May 6, 2026 22:03
@abcxff abcxff force-pushed the 05-06-fix_rivetkit_bind_methods_through_createwritethroughproxy branch from 3be897d to c39972c Compare May 7, 2026 20:16
@abcxff abcxff force-pushed the 05-06-fix_log_error_on_failed_inspector_requests branch from fc4756e to 5ec0839 Compare May 7, 2026 20:16
@abcxff abcxff marked this pull request as ready for review May 7, 2026 20:19
@abcxff abcxff requested a review from NathanFlurry May 7, 2026 20:23
@abcxff abcxff force-pushed the 05-06-fix_log_error_on_failed_inspector_requests branch from 5ec0839 to 4ff7adb Compare May 11, 2026 03:41
@abcxff abcxff force-pushed the 05-06-fix_rivetkit_bind_methods_through_createwritethroughproxy branch from c39972c to add44af Compare May 11, 2026 03:42
@abcxff abcxff changed the base branch from 05-06-fix_log_error_on_failed_inspector_requests to graphite-base/4987 May 12, 2026 13:21
@abcxff abcxff force-pushed the 05-06-fix_rivetkit_bind_methods_through_createwritethroughproxy branch from add44af to 4455290 Compare May 12, 2026 13:21
@abcxff abcxff changed the base branch from graphite-base/4987 to 05-12-feat_rivetkit_add_connectionmap_readonly_map_wrapper_for_actor_connections May 12, 2026 13:21
@abcxff abcxff changed the base branch from 05-12-feat_rivetkit_add_connectionmap_readonly_map_wrapper_for_actor_connections to graphite-base/4987 May 13, 2026 18:45
@abcxff abcxff force-pushed the graphite-base/4987 branch from b18ec51 to 4fbdc66 Compare May 13, 2026 18:48
@abcxff abcxff force-pushed the 05-06-fix_rivetkit_bind_methods_through_createwritethroughproxy branch from 77e9b2f to ce604d2 Compare May 13, 2026 18:48
@graphite-app graphite-app Bot changed the base branch from graphite-base/4987 to main May 13, 2026 18:48
@abcxff abcxff force-pushed the 05-06-fix_rivetkit_bind_methods_through_createwritethroughproxy branch from ce604d2 to 342a262 Compare May 13, 2026 18:48
@abcxff abcxff force-pushed the 05-06-fix_rivetkit_bind_methods_through_createwritethroughproxy branch from 342a262 to 3d9e480 Compare May 13, 2026 19:11
@abcxff abcxff changed the base branch from main to graphite-base/4987 May 13, 2026 20:07
@abcxff abcxff force-pushed the 05-06-fix_rivetkit_bind_methods_through_createwritethroughproxy branch from 3d9e480 to 8c70217 Compare May 13, 2026 20:07
@abcxff abcxff changed the base branch from graphite-base/4987 to 05-13-fix_rivetkit_decode_bridge_errors_with_actor_null May 13, 2026 20:08
@abcxff abcxff force-pushed the 05-13-fix_rivetkit_decode_bridge_errors_with_actor_null branch from 8708f6b to e2a084d Compare May 13, 2026 20:09
@abcxff abcxff force-pushed the 05-06-fix_rivetkit_bind_methods_through_createwritethroughproxy branch from 8c70217 to 00af8a0 Compare May 13, 2026 20:09
@abcxff abcxff changed the base branch from 05-13-fix_rivetkit_decode_bridge_errors_with_actor_null to graphite-base/4987 May 13, 2026 20:13
@abcxff abcxff force-pushed the graphite-base/4987 branch from e2a084d to 52e743c Compare May 13, 2026 20:15
@abcxff abcxff force-pushed the 05-06-fix_rivetkit_bind_methods_through_createwritethroughproxy branch from 00af8a0 to ad1ebe6 Compare May 13, 2026 20:15
@graphite-app graphite-app Bot changed the base branch from graphite-base/4987 to main May 13, 2026 20:15
@abcxff abcxff force-pushed the 05-06-fix_rivetkit_bind_methods_through_createwritethroughproxy branch from ad1ebe6 to 6d4d578 Compare May 13, 2026 20:15
@abcxff abcxff changed the base branch from main to graphite-base/4987 May 14, 2026 18:09
@abcxff abcxff force-pushed the 05-06-fix_rivetkit_bind_methods_through_createwritethroughproxy branch from 6d4d578 to d02776c Compare May 14, 2026 18:09
@abcxff abcxff changed the base branch from graphite-base/4987 to 05-14-fix_rivetkit_revert_remove_client-side_connection_request_size_check May 14, 2026 18:09
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