Skip to content

fix(signer): serialize object payloads in host error messages - #423

Merged
TarikGul merged 2 commits into
mainfrom
rh/signer-format-error-payload
Oct 5, 2026
Merged

TarikGul merged 2 commits into
mainfrom
rh/signer-format-error-payload

Conversation

@ReinhardHatko

Copy link
Copy Markdown
Contributor

Problem

formatError in packages/signer/src/providers/host.ts builds the message of every Host rejected … request error and the failed to get product account log line. When a tag's value is a plain object, or a tagless error has neither reason nor message, it falls back to String(). So the payload prints as [object Object]. A Coffer deployment on the paseo.li web host logged:

[signer:host] failed to get product account {"error":"Host rejected product account request: Domain → V1 → Unknown ([object Object])"}

The host's reason for rejecting the request is lost from both the message and the log line. HostRejectedError.cause still holds it, but only callers that log the error object itself can see it.

Change

A local describeValue replaces the two String() fallbacks. It returns:

  • primitives unchanged;
  • objects as JSON, with:
    • bigints as decimal strings;
    • byte arrays as hex, long ones cut to their first 64 bytes plus the length, so the fields after a blob survive the cap;
    • a nested Error as its name, message and cause, which JSON.stringify would otherwise drop;
    • an object met twice as [seen], which also cuts cycles;
  • output past 500 characters elided.

A { reason } payload under a tag now reads as its reason (V1 → Unknown → no account), as a tagless { reason } already did. Every output that was not [object Object] before is unchanged.

Tests

Seven new cases in the in-source formatError suite:

  • the Domain → V1 → Unknown nesting with an object payload;
  • a reason payload under a tag;
  • a tagless object;
  • bigints, bytes and a cycle;
  • the exact elided output;
  • a long byte array followed by another field;
  • a nested Error with a cause.

Checks: pnpm --filter @parity/product-sdk-signer test passes 156 tests, typecheck exits 0, and pnpm check is clean.

The changeset is a patch for @parity/product-sdk-signer, parked in pending-changesets/.

🤖 Generated with Claude Code

formatError fell back to String() for a tag whose value is a plain
object, and for a tagless object with neither reason nor message, so a
product-account rejection logged as "Host rejected product account
request: Domain → V1 → Unknown ([object Object])" and the host's payload
was lost from the message and the log line.

Object payloads are now serialized as JSON: bigints as strings, byte
arrays as hex with long ones cut to their first 64 bytes (so the fields
after a blob survive the 500-character cap), a nested Error as its name,
message and cause (JSON.stringify drops them), cycles cut. A { reason }
payload under a tag reads as its reason, as a tagless one already did.
@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

📦 Bundle size impact

Comparing 2026-10-05T17:05:26.889Z → 2026-10-05T17:03:57.445Z

Package Entry Bundled before Bundled after Δ Ship gzip Δ Shake ratio
🟢 @parity/product-sdk . 8.86 MB 8.86 MB +671 B (+0.0%) 0 B 0% (was 0%)
🟢 @parity/product-sdk ./core 8.86 MB 8.86 MB +671 B (+0.0%) 0 B 0% (was 0%)
🟢 @parity/product-sdk ./individuality 84.9 KB 85.1 KB +153 B (+0.2%) 0 B —
🟢 @parity/product-sdk ./nfts 26.2 KB 26.3 KB +147 B (+0.5%) 0 B —
🟢 @parity/product-sdk ./react 8.87 MB 8.87 MB +671 B (+0.0%) 0 B 0% (was 0%)
🟢 @parity/product-sdk ./renderer 12.7 KB 10.1 KB -2.5 KB (-19.9%) 0 B —
🟢 @parity/product-sdk ./wallet 208.6 KB 209.2 KB +659 B (+0.3%) 0 B 0% (was 0%)
🟢 @parity/product-sdk-signer . 208.6 KB 209.2 KB +659 B (+0.3%) +445 B 0% (was 0%)
🟢 @parity/product-sdk-terminal . 696.0 KB 696.9 KB +848 B (+0.1%) +362 B 39% (was 39%)

Thresholds — 🟡 ≥10% or ≥5.0 KB · 🟠 ≥20% or ≥15.0 KB (bundled). Percentage only applies once the baseline is ≥ 10 KB. Informational — this check never blocks merge.

Comment thread product-sdk/packages/signer/src/providers/host.ts Outdated
describeValue rebuilt a nested Error from name, message and cause, so an
RPC error's code and data never reached the message, though they are
usually what the log needs. It now copies the error's own enumerable
fields too, after name and message and before cause, so a long cause
cannot push them past the 500-character cut.

The WeakSet also marks a shared object that is not a cycle, so its
"[seen]" label read like a cycle. Rename it "[repeated]", which fits
both. Tracking only the enclosing objects instead would print a shared
object once per parent, which grows exponentially on a graph that shares
heavily; the WeakSet keeps the walk linear.
@TarikGul
TarikGul merged commit ce68d86 into main Oct 5, 2026
13 of 14 checks passed
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.

3 participants