Skip to content

feat(request): keep the reply that describes an IQ error - #1257

Merged
jlucaso1 merged 5 commits into
mainfrom
claude/iqerror-preserve-response-tnwh91
Aug 8, 2026
Merged

feat(request): keep the reply that describes an IQ error#1257
jlucaso1 merged 5 commits into
mainfrom
claude/iqerror-preserve-response-tnwh91

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

send_iq_node hands back the whole Arc<OwnedNodeRef> when the server answers type="result". When the same request comes back type="error", the caller got IqError::ServerError { code, text, error_type, backoff } and the stanza was thrown away, so whether a reply arrived intact or as a four-field summary depended only on its type attribute. Everything outside those four names went with it: further <error> attributes, the child elements XMPP allows there, and the raw bytes, which are the only faithful material for recording or replaying a rejection. ServerError now carries the reply as the node the receive path had already decoded, which makes the rejection path one refcount bump and leaves the success path with nothing new to do. The four extracted fields stay alongside it, because nearly every match in the tree is ServerError { code, .. } and dropping them would make all of those reparse the common case for nothing.

Changes

  • Breaking. IqError::ServerError gains response: RejectionStanza, the rejection stanza verbatim. Matching with .. is unaffected; constructing the variant now needs the stanza. Migration: pass the response you were rejected with (Arc<OwnedNodeRef> converts via .into()), or in a fixture build one and decode it through OwnedNodeRef::new.
  • RejectionStanza wraps Arc<OwnedNodeRef> rather than Box<Node>, the shape Disconnected uses: the receive path already holds the Arc, so there is no copy, and it keeps backing_bytes(). Box<Node> would have deep-copied a node we already own and would have dropped the bytes on the floor.
  • The wrapper exists for its Debug, which names the stanza (<iq>) instead of printing it. Background IQ failures are logged with {e:?} at warn level on the connect path (node_io.rs: the post-connect active IQ, props, blocklist, privacy settings), and an error stanza can carry a JID in its attributes or children; deriving Debug straight through the node would have written all of that into production logs, where before only the four summarized fields went. Deref keeps every accessor reachable, so reading the contents is unchanged for a caller that wants them, and as_arc() / into_arc() hand back the shareable node.
  • Breaking. From<wacore::request::IqError> for IqError is replaced by IqError::from_response(err, &response). A From cannot take the response, and converting without it is exactly the loss being removed here. Migration: err.into() becomes IqError::from_response(err, &response).
  • send_and_wait_iq passes the node it just classified instead of dropping it. The Ok arm is byte-for-byte what it was and still returns that same allocation.
  • execute needed nothing: it propagates IqError untouched, so there was no second place discarding the stanza. Neither wacore's own IqError nor parse_iq_response changed, since that layer only ever holds a borrowed NodeRef and making it own a copy would have cost the deep copy this avoids.
  • The two call sites that destructured all four fields, server_rejection_of in error.rs and the prekey rewrap in context_impl.rs, gained a ... No behaviour change; every other call site already used ...
  • Test fixtures build the variant through test_utils::server_error_iq, which runs the real parse_iq_response over a real <iq type="error">, so a fixture cannot drift from what the server actually produces. Existing tests changed only where they constructed the variant by hand, which is the contract change itself and not an adaptation to make them pass.

New tests in request.rs: a rejection carrying an unmodelled <error> attribute, an <error> child and an <iq>-level attribute, all asserted on the preserved node rather than on the error string, with the four fields checked for parity and the redacted Debug pinned in the same test; the corresponding failure with type and backoff absent; and Arc::ptr_eq on both the type="result" and type="error" paths, which is what proves neither picked up a second parse.

Validation

cargo fmt --all
cargo test -p whatsapp-rust --lib             # 1444 passed, 0 failed
cargo test -p whatsapp-rust --test error_surface   # 26 passed, 0 failed
cargo clippy -p whatsapp-rust --all-targets -- -D warnings   # clean

Full matrix left to CI.

A successful IQ hands the caller the whole response node. A rejected one was
reduced to four extracted attributes and the stanza itself was dropped, so
anything outside code/text/type/backoff (further <error> attributes, the child
elements XMPP allows, the original bytes) was gone before the caller could look
at it. Which part of a protocol error matters is the caller's judgement, not
something the IQ transport should decide for them.

ServerError now carries the reply as the Arc<OwnedNodeRef> the receive path had
already decoded, so the rejection path costs one refcount bump and the success
path gains no work at all. Box<Node>, the shape Disconnected uses, would have
meant deep-copying a node we already own and would have lost backing_bytes(),
which is the only faithful material for recording or replaying a rejection.

The four extracted fields stay. Nearly every call site in the tree matches
ServerError { code, .. } and reads nothing else, so removing them would make all
of those reparse the common case for no gain.

From<wacore::request::IqError> gives way to IqError::from_response(err,
&response): converting a classification failure without the response it was read
from is precisely the loss this removes.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 31745ef4-8a20-4b97-90d1-c4e2e30dfe29

📥 Commits

Reviewing files that changed from the base of the PR and between 28d2e01 and 9f21e56.

📒 Files selected for processing (1)
  • wacore/src/request.rs

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Server errors now preserve the complete rejected response, including optional attributes and original response details.
    • Improved handling of server-error responses during parsing and conversion.
    • Error details remain available for inspection instead of being treated as discarded.
  • Tests

    • Expanded coverage for preserved error content, response identity, and rejection scenarios.
    • Standardized server-error test fixtures for more consistent validation.

Walkthrough

IqError::ServerError now preserves the complete rejected IQ response. The conversion API accepts the response allocation, test utilities construct parsed server errors, and existing error handling uses shared fixtures. IQ parser terminology now describes unread details.

Changes

IQ error response preservation

Layer / File(s) Summary
Preserve rejected response stanzas
src/request.rs, src/lib.rs
RejectionStanza wraps the original response allocation. IqError::ServerError stores it, and from_response preserves the response during classification.
Build response-backed test fixtures
src/test_utils.rs, tests/error_surface.rs
Test utilities construct parsed server errors and return delivered response allocations. Error-surface fixtures include decoded rejection stanzas.
Update error consumers and fixtures
src/client/..., src/error.rs, src/features/..., src/keepalive.rs, src/pair_code.rs
Server-error matches ignore additional fields. Existing tests use server_error_iq.
Rename unread detail handling
wacore/src/request.rs
Parser helpers, warning state, comments, and tests use “unread” terminology. Warning text states that details remain available on the stanza.

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

Sequence Diagram(s)

sequenceDiagram
  participant IQWaiter
  participant RequestResponse
  participant IqError
  IQWaiter->>RequestResponse: deliver owned IQ response
  RequestResponse->>IqError: from_response(error, response)
  IqError->>IqError: store response allocation
Loading

Possibly related PRs

Suggested labels: api-design, breaking-change

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: preserving IQ error replies in the request layer.
Description check ✅ Passed The description directly explains the preserved IQ error stanza, API changes, call-site updates, tests, and validation results.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/iqerror-preserve-response-tnwh91

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.

@greptile-apps

greptile-apps Bot commented Aug 8, 2026

Copy link
Copy Markdown

Greptile Summary

The PR preserves the original decoded IQ rejection stanza alongside the existing summarized server-error fields, allowing callers to inspect unmodeled attributes, children, and backing bytes without reparsing.

  • Adds a redacted RejectionStanza wrapper over Arc<OwnedNodeRef> and exports it publicly.
  • Passes the decoded response through IQ error conversion while retaining the existing scalar error fields.
  • Updates affected fixtures and tests, including allocation-identity and stanza-preservation coverage.
  • Renames the core diagnostic from “dropped” to “unread” error detail to reflect that callers can now access the original stanza.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
src/request.rs Introduces the redacted rejection-stanza wrapper and attaches the original decoded response to server IQ errors, with focused identity and preservation tests.
wacore/src/request.rs Renames dropped-detail diagnostics to unread-detail diagnostics without changing response classification behavior.
src/test_utils.rs Adds parser-backed server-error fixtures and returns delivered IQ allocations so tests can verify pointer identity.
src/lib.rs Re-exports RejectionStanza through the crate root and prelude.
tests/error_surface.rs Updates public error-surface fixtures to construct the newly required rejection stanza.
src/client/context_impl.rs Keeps existing scalar error translation compatible with the expanded non-exhaustive ServerError variant.
src/error.rs Preserves existing ServerRejection extraction while ignoring the newly attached raw stanza.

Sequence Diagram

sequenceDiagram
  participant Server as WhatsApp Server
  participant Receive as Receive path
  participant Parser as parse_iq_response
  participant Client as send_iq_node caller
  Server->>Receive: IQ response bytes
  Receive->>Receive: "Decode into Arc<OwnedNodeRef>"
  Receive->>Parser: Borrow decoded response
  alt "type="result""
    Parser-->>Receive: Ok
    Receive-->>Client: "Same Arc<OwnedNodeRef>"
  else "type="error""
    Parser-->>Receive: ServerError summary
    Receive->>Receive: Attach RejectionStanza(response.clone())
    Receive-->>Client: IqError::ServerError with summary and stanza
  end
Loading

Reviews (5): Last reviewed commit: "docs(request): finish the unread rename ..." | Re-trigger Greptile

greptile-apps[bot]
greptile-apps Bot previously approved these changes Aug 8, 2026

@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: 2

🤖 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 `@src/request.rs`:
- Around line 631-648: Update the server-error test around
IqError::from_response to capture the Node returned by answer_iq as delivered,
capture the parsed response, and assert Arc::ptr_eq(&response, &delivered)
alongside the existing content assertions. Apply the same allocation-identity
assertion to the corresponding test block around the alternate server-error
path.
- Around line 121-124: Update the public documentation for the
IqError::ServerError response field so it describes the caller-facing stanza as
type="error", not type="result"; leave the response representation and
surrounding behavior unchanged.
🪄 Autofix

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: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2a28e9f6-35f7-46e1-94f2-4a4847fee83b

📥 Commits

Reviewing files that changed from the base of the PR and between 4b34ca5 and e720961.

📒 Files selected for processing (10)
  • src/client/context_impl.rs
  • src/client/sessions.rs
  • src/error.rs
  • src/features/mex.rs
  • src/features/rotate_key.rs
  • src/keepalive.rs
  • src/pair_code.rs
  • src/request.rs
  • src/test_utils.rs
  • tests/error_surface.rs

Comment thread src/request.rs Outdated
Comment thread src/request.rs Outdated

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 10 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/request.rs Outdated
The field doc read as if the preserved reply were the type="result" one;
the analogy with the success path was the point, not the type. Say
type="error" outright, and assert Arc::ptr_eq on the rejection path too,
so the test proves the error path attaches the decoded node rather than a
copy of it.
@greptile-apps
greptile-apps Bot dismissed their stale review August 8, 2026 03:54

Dismissed because a newer commit was pushed; Greptile will re-review the current head.

greptile-apps[bot]
greptile-apps Bot previously approved these changes Aug 8, 2026

@cubic-dev-ai cubic-dev-ai 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.

0 issues found across 1 file (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Requires human review: This PR intentionally breaks the public IqError::ServerError variant by adding a response field and removes the From<wacore::request::IqError> impl. A human must decide whether that API and migration break is acceptable for downstream consumers.

Re-trigger cubic

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown

📦 Binary size report

Metric main PR Δ
bin size (stripped) 10.02 MiB 10.03 MiB +2.69 KiB (+0.03%) 🔺
bin .text 8.03 MiB 8.03 MiB +2.62 KiB (+0.03%) 🔺
bin allocated (text+data+bss) 10.02 MiB 10.03 MiB +3.98 KiB (+0.04%) 🔺
llvm-lines wacore 520,786 520,786 0
llvm-lines wacore copies 17,014 17,014 0
llvm-lines whatsapp-rust lib 742,962 743,127 +165 (+0.02%) 🔺
llvm-lines whatsapp-rust lib copies 23,309 23,311 +2 (+0.01%) 🔺
deps crates (Cargo.lock) 462 462 0
.text per crate
Crate main PR Δ
.text whatsapp_rust 1.82 MiB 1.83 MiB +2.64 KiB (+0.14%) 🔺
.text wacore 691.75 KiB 691.75 KiB 0
.text wacore_binary 88.22 KiB 88.22 KiB 0
.text wacore_libsignal 178.88 KiB 178.88 KiB 0
.text wacore_appstate 22.35 KiB 22.35 KiB 0
.text wacore_noise 20.94 KiB 20.94 KiB 0
.text waproto 1.74 MiB 1.74 MiB 0
.text whatsapp_rust_sqlite_storage 540.30 KiB 540.30 KiB 0
.text whatsapp_rust_tokio_transport 40.49 KiB 40.49 KiB 0
.text whatsapp_rust_ureq_http_client 12.68 KiB 12.68 KiB 0
.text std 992.99 KiB 993.06 KiB +68 B (+0.01%) 🔺
.text other deps 1.90 MiB 1.90 MiB 0
Top movers (cargo-bloat attribution)
Crate main PR Δ
whatsapp_rust 1.82 MiB 1.83 MiB +2.64 KiB (+0.14%)

Baseline: c63988850 (latest main run) · Head: de1dedddf · Graphs

@jlucaso1

jlucaso1 commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator 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: a95a460aa2

ℹ️ 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 src/request.rs Outdated
Comment on lines +121 to +124
/// The `type="error"` stanza verbatim, handed over whole the way a `type="result"`
/// one is. What matters in a protocol error is the caller's judgement, so the summary
/// above does not replace it: unread attributes, children and bytes stay readable.
response: Arc<wacore_binary::OwnedNodeRef>,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Redact the preserved stanza from debug output

When a background IQ is rejected, callers such as set_passive are logged with {e:?} at warning level in src/client/node_io.rs; deriving Debug now recursively formats this response, and OwnedNodeRef::Debug emits the complete decoded node. Consequently, error attributes and children containing JIDs or other PII are newly written to production warning logs, whereas the previous debug representation contained only the four summarized fields. Keep the response accessible to callers, but use a custom/redacted Debug representation that omits its contents.

Useful? React with 👍 / 👎.

Comment thread src/request.rs
Comment on lines 524 to +526
Ok(Ok(response_node)) => match request_utils.parse_iq_response(response_node.get()) {
Ok(()) => Ok(response_node),
Err(e) => Err(e.into()),
Err(e) => Err(IqError::from_response(e, &response_node)),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Stop warning that preserved error details were dropped

When an error stanza contains an extra attribute, child, or payload, parse_iq_response still emits the process-wide warning that the parser dropped those details before this arm attaches the original stanza to IqError. After this change the caller can inspect every one of those details, so a valid detailed rejection now produces a misleading operational warning claiming data loss; use a non-warning classification path here or update the probe so it reflects that this caller preserves the response.

Useful? React with 👍 / 👎.

Background IQ failures are logged with {e:?} at warn level on the connect
path (node_io.rs: the post-connect active IQ, props, blocklist, privacy
settings). Deriving Debug through the preserved response meant a rejected
one of those newly wrote the whole decoded stanza into production logs,
JIDs in its attributes and children included, where before it wrote only
the four summarized fields.

Wrap the node in RejectionStanza, whose Debug names the stanza and stops
there. Deref keeps every accessor reachable, so reading the contents is
unchanged for a caller that wants them; it is just no longer what a log
line does by default.
@greptile-apps
greptile-apps Bot dismissed their stale review August 8, 2026 06:39

Dismissed because a newer commit was pushed; Greptile will re-review the current head.

greptile-apps[bot]
greptile-apps Bot previously approved these changes Aug 8, 2026

@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: 23ca4b4180

ℹ️ 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 src/request.rs
Comment on lines +216 to +219
pub fn from_response(
err: wacore::request::IqError,
response: &Arc<wacore_binary::OwnedNodeRef>,
) -> Self {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the public From conversion

Replacing impl From<wacore::request::IqError> for IqError with this inherent method breaks downstream code that uses .into(), IqError::from, or ? to convert core IQ errors, even for variants such as Timeout that need no response stanza. from_response is not source-compatible because it requires a second argument; retain the trait conversion, potentially by making the preserved response optional for errors converted without a stanza.

Useful? React with 👍 / 👎.

@cubic-dev-ai cubic-dev-ai 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.

0 issues found across 3 files (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Requires human review: This is a breaking public API change: IqError::ServerError gains a new field and the From conversion is replaced by from_response(), so external callers must migrate. Such contract changes need human sign-off.

Re-trigger cubic

The probe's warning predates the stanza reaching the caller. It reported
what `parse_iq_response` does not read as detail the parser "drops", and
its rationale said the finding was not something the calling application
could act on. Neither holds: the node is borrowed, never consumed, so
every caller still holds what went unread — and on this crate's Tokio
side that stanza is now `IqError::ServerError`'s `response`.

Renames the probe to match the word `PARSED_ERROR_ATTRS` already used
for the same idea, so "dropped" stops describing a loss that does not
happen. Private to the module; no API moves.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013HuzUxrdsyfcBA4cSUuizi
@greptile-apps
greptile-apps Bot dismissed their stale review August 8, 2026 07:34

Dismissed because a newer commit was pushed; Greptile will re-review the current head.

greptile-apps[bot]
greptile-apps Bot previously approved these changes Aug 8, 2026

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 1 file (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread wacore/src/request.rs
The rename left one prose line behind, describing the fully-parsed case
as one where nothing is "dropped".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013HuzUxrdsyfcBA4cSUuizi
@greptile-apps
greptile-apps Bot dismissed their stale review August 8, 2026 07:44

Dismissed because a newer commit was pushed; Greptile will re-review the current head.

@cubic-dev-ai cubic-dev-ai 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.

0 issues found across 1 file (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Auto-approved: This is a focused refactor that adds a RejectionStanza field to ServerError and preserves the full error stanza from server IQ failures, with bounded changes and extensive tests.

Re-trigger cubic

@jlucaso1
jlucaso1 merged commit d647b17 into main Aug 8, 2026
25 of 26 checks passed
@jlucaso1
jlucaso1 deleted the claude/iqerror-preserve-response-tnwh91 branch August 8, 2026 12:11
jlucaso1 added a commit to oxidezap/whatsapp-rust-docs that referenced this pull request Aug 8, 2026
…ield

Reflects oxidezap/whatsapp-rust#1257: IqError::ServerError gains a
response: RejectionStanza field carrying the rejection stanza verbatim,
and From<wacore::request::IqError> is replaced by IqError::from_response.
jlucaso1 added a commit to oxidezap/whatsapp-rust-docs that referenced this pull request Aug 8, 2026
…ld (#499)

* docs(errors): document RejectionStanza and the ServerError.response field

Reflects oxidezap/whatsapp-rust#1257: IqError::ServerError gains a
response: RejectionStanza field carrying the rejection stanza verbatim,
and From<wacore::request::IqError> is replaced by IqError::from_response.

* docs(errors): address review — E0027 exhaustive-match break, fixture unpack step

- Note that a ServerError match binding all four former fields without `..`
  now fails E0027 once `response` is added, not just hand-constructed variants.
- Show the required unpack() step when building a fixture from marshal()
  output for OwnedNodeRef::new, which expects the format byte already stripped.

* docs(errors): fully qualify NodeBuilder in fixture snippet

Matches the fully-qualified style already used for marshal/unpack in the
same snippet, per cubic's review — the bare NodeBuilder reference wasn't
importable as written.
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.

2 participants