Skip to content

fix: route wallet list summary to stderr, emit JSON on stdout - #576

Open
erhnysr wants to merge 2 commits into
nansen-ai:mainfrom
erhnysr:fix/wallet-list-stdout-json
Open

fix: route wallet list summary to stderr, emit JSON on stdout#576
erhnysr wants to merge 2 commits into
nansen-ai:mainfrom
erhnysr:fix/wallet-list-stdout-json

Conversation

@erhnysr

@erhnysr erhnysr commented Sep 4, 2026

Copy link
Copy Markdown

The bug

nansen wallet list writes its human-readable summary (wallet names, EVM/Solana addresses, the default marker) to stdout via log(), and returns undefined so no structured JSON is emitted. That mixes prose and data on the same stream, so agent pipelines that expect parseable output break:

$ nansen wallet list | jq .
parse error: Invalid numeric literal at line 1, column 5

Every other data-returning command emits clean JSON on stdout; wallet list is the odd one out, which makes it unusable in | jq-style automation.

Prior history — why this keeps coming back

This was reported before in #289 and #406, and both were closed on the belief that the fix had already landed via #397. That belief is incorrect, and I verified it two ways before writing this:

  • refactor: extract requireWalletFile() and deriveEvmAddress() helpers #397's actual diff only extracted a requireWalletFile() helper for the file-existence checks in showWallet, exportWallet, setDefaultWallet, and deleteWallet (plus a deriveEvmAddress() helper in transfer.js). It is a self-described "no behavior changes" refactor and never touches listWallets or the list handler.
  • A full git log -L :listWallets:src/wallet.js trace shows listWallets was only ever touched by two commits: its original creation, and one Privy PR that added a provider field. No commit in its history ever introduced stderr/stdout separation.

So this is a fresh fix, not a rebase or reopen of the older PRs — the output path they were meant to fix was never actually modified.

The fix

In the list handler (src/wallet.js):

  • Route the human-readable summary to stderr via process.stderr.write (the file's existing convention).
  • Return a structured { wallets } value so runCLI's normal success path renders clean JSON to stdout — mirroring how research/data commands emit their output.
  • The empty case prints its No wallets found hint to stderr and still returns { wallets: [] }, so stdout is always valid JSON.

After the fix:

$ nansen wallet list | jq '.data.wallets[].name'
"main"

(the summary still renders for humans — it's just on stderr now.)

Testing & verification

  • Failing-first test added driving the real runCLI: before the fix it fails with SyntaxError: Unexpected end of JSON input (the summary went to real stdout, the JSON sink was empty) — reproducing the exact bug.
  • After the fix, the wallet-list tests pass (4 passing), including an updated Privy test that now asserts the provider tag lands on stderr.
  • Full suite: 2623 passed, 2 skipped, 0 failed (64 files).
  • npm run lint: clean.
  • Changeset added (patch), user-facing.

The list handler printed its human-readable summary (names, EVM/Solana
addresses, default marker) to stdout via log(), so agents could not
JSON.parse the output. Route the summary to stderr and return a
structured { wallets } value so runCLI emits clean JSON on stdout,
matching how research commands emit their data. The empty case prints
its hint to stderr and still emits { "wallets": [] } on stdout.
@nansen-pr-reviewer

nansen-pr-reviewer Bot commented Sep 4, 2026

Copy link
Copy Markdown

pr-reviewer Summary for #e9d9814

No issues found

The code review completed successfully with no findings.

Review effort: 2/5 (Simple)

Summary

This PR correctly fixes nansen wallet list polluting stdout with its human-readable summary, making it unparseable by agent pipelines. The fix is clean and complete: the list handler now routes all display output to process.stderr.write (consistent with the existing WARNING: and deprecation-notice patterns elsewhere in the file) and returns { wallets } so runCLI's normal success path wraps it in { success: true, data: { wallets } } on stdout — matching the test assertion of parsed.data.wallets. The empty-list case is handled correctly, always emitting valid JSON ({ "wallets": [] }) regardless of whether wallets exist.

Supporting changes are all correct:

  • AGENTS.md — the exception note is clear and will guide future contributors.
  • schema.json — the returns array accurately reflects the listWallets() shape.
  • Changeset — correct package name, patch semver is right for a bug fix, description is accurate.
  • Tests — the new wallet list stdout/stderr separation suite drives runCLI end-to-end, asserts JSON.parse-ability on stdout, and verifies the human summary stays on stderr. The vi.restoreAllMocks() addition to the Privy afterEach is a good defensive fix that prevents spy leakage across tests.

Token usage: 2,429 input, 4,026 output, 402,589 cache read, 32,139 cache write | Usage Guide

New pushes are reviewed automatically with a 10-minute cooldown between reviews. To request a review at any time, comment @nansen-pr-reviewer re-review.

nansen-pr-reviewer[bot]
nansen-pr-reviewer Bot previously approved these changes Sep 4, 2026

@nansen-pr-reviewer nansen-pr-reviewer 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.

Auto-approved

This PR was automatically approved because:

  • Claude recommends approval
  • Claude assessed this as a moderate effort change
  • The effort level is within the auto-approval threshold of 2
  • No high or critical issues were detected
  • Review comment contains non-blocking feedback

If you have any concerns, please request a manual review.

@erhnysr

erhnysr commented Sep 4, 2026

Copy link
Copy Markdown
Author

Prior attempts at this same fix, both closed on the mistaken belief it had landed via #397:

I left a note on each explaining why #397 did not actually fix it (it only refactored the existence-check helper and never touched listWallets), and pointing here. Cross-linking so the full history is visible from this PR.

@nansen-pr-reviewer nansen-pr-reviewer 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.

Auto-approved

This PR was automatically approved because:

  • Claude recommends approval
  • Claude assessed this as a moderate effort change
  • The effort level is within the auto-approval threshold of 2
  • No high or critical issues were detected

If you have any concerns, please request a manual review.

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