Debugging
nautilus redacts what it passes on from the node, the wallet, the verifier and the RPC: error messages, warnings and log events never carry an RPC token, a session id, a signature or a JWT, at any log level. That keeps them safe to log and to show, but sometimes the redacted text is not enough to find out what went wrong. The sensitive debug channel shows the raw values, on a separate channel, only when you turn it on.
When to use it
- A credential-gated download fails with
Presentation failed, and you need the verifier's per-policy results (which policy failed, and why). - The node refuses a request, and its full answer would explain more than the one sentence in the error.
- An RPC call fails, and you need its response body (and, at
secrets, which URL it went to). - A store refuses an upload, and you need its answer.
Turning it on
In Node, without changing code:
NAUTILUS_DEBUG=sensitive node download.js 2> debug.logOr in code, also in a browser:
import { } from '@deltadao/nautilus'
.({ : 'sensitive', : ['policy', 'wallet'] })
// … reproduce the failure …
.({ : 'off' }) setDebug overrides the environment variables. See
setDebug for every option.
The two tiers
| Tier | Adds | Typical use |
|---|---|---|
off (default) | nothing | production |
sensitive | raw node, wallet, verifier and RPC answers; session ids and redirect URIs; the verifier's per-policy results with full errors; request payloads with their signatures and nonces; raw signed transactions; presented VP/VC JWTs; storage pointers without their credentials | access, SSI, ordering and indexing problems |
secrets | everything in sensitive, plus long-lived credentials: the RPC URL's token, S3 secret keys, IPFS (Pinata) JWTs, Authorization headers | store or RPC authentication problems |
sensitive still hides long-lived credentials, best effort, by name and shape: an RPC URL
reads https://rpc.example.org/v3/<redacted> (a local one without a scheme too,
127.0.0.1:8545/v3/<redacted>, also with a BOM or a zero-width character in its path), and
so does any value under a name ending in
token, key, secret, password, auth, cookie, jwt or credential(s) (apiToken,
pinataGatewayToken, drpc's dkey, S3_SECRET_KEY=, PINATA_JWT=, a Cookie header), in
URLs, objects and text, also in JSON quoted inside JSON (up to eight levels of JSON escaping
or percent-encoding undone; deeper JSON escaping and percent-encoding, also mixed, are
flattened). Session-scoped values stay readable (vp_token, state, sessionId, nonce, signature, id). A credential under a name
nautilus does not know, or without a name, still prints: scan the output before you share
it. A long id in a URL path (20 or more letters and digits, such as a 64-hex compute job id
without 0x, an Arweave id or an S3 object key segment) is masked like an API key in a
path, https://…/v3/<key>; the secrets tier shows it. secrets must be named explicitly:
no other value of NAUTILUS_DEBUG reaches it.
nautilus never holds the signer's private key or mnemonic (your signer does), so neither is printed at any tier.
Topics
node, wallet, policy, rpc, order, nonce, store and compute. Narrow the output
with NAUTILUS_DEBUG_TOPICS=policy,wallet or topics: ['policy', 'wallet']. An unknown topic
in NAUTILUS_DEBUG_TOPICS is ignored with a config.env.invalid warning; when none of the
topics given is known, no topic prints (never all of them).
nautilus's own log events hand their raw fields to the
channel too, but only when the event is printed at the current log level: pair
NAUTILUS_DEBUG with NAUTILUS_LOG_LEVEL=debug (or configureLogging({ level: 'debug' }))
to see the raw fields of every event.
What you see
Each event is one line on stderr (in a browser, through console.info, which consoles show
by default), prefixed with its tier and topic, so the lines are easy to find, split off and
delete. A string in an event is cut at 32,768 characters and an event at about 256 KB, after
the masking; a longer string is read only up to the last word before the cut, so a
credential is never shown in part. A cut string ends in …(cut: N of M characters shown):
[nautilus] WARNING: sensitive debug logging is ON: the output contains session ids, signatures and request data. Don't use it in production, and don't share the output.
[nautilus:debug:sensitive] [policy] 2026-10-10T13:36:58.561Z [access 3f9a2c1b07e4] policy-server session opened (initiate) {…}
[nautilus:debug:sensitive] [wallet] 2026-10-10T13:36:58.759Z [access 3f9a2c1b07e4] answer HTTP 400 to wallet/…/exchange/usePresentationRequest {"status":400,"statusText":"Bad Request","body":"{\"redirectUri\":\"…/error?id=…\",…}"}
[nautilus:debug:sensitive] [policy] 2026-10-10T13:36:58.772Z [access 3f9a2c1b07e4] verifier per-policy results (checkSessionId) {…,"policies":[…,"VerifiablePresentation holder-binding: failed (java.lang.NullPointerException)"]}
The error your code catches is the same with the channel on or off: here a
PolicyDeniedError naming holder-binding: java.lang.NullPointerException, read from the
verifier's record (checkSessionId) that nautilus asks for after a presentation the verifier
refused. Its cause keeps only the wallet's errorMessage; the wallet's redirect URI and the
session id are in the debug lines only. Turning the channel on changes nothing in what
nautilus sends.
To route the events elsewhere, pass a sink; it receives { time, tier, topic, message, data }, and within an operation its opId and operation. In Node 22.3 or newer, a debug line made
during a call shows the call and its opId after the time ([access 3f9a2c1b07e4]), the same
opId as the call's log events and as error.opId on the error it throws, so the raw data
of one failing call can be picked out of a busy log.
The warnings
Turning a tier on prints a warning on stderr, once per tier, that no log level or log sink
suppresses. Under NODE_ENV=production it is stronger. Treat either as a reminder to turn the
channel off again.
Sharing debug output
The output is meant for you, on your machine. Before you share any of it, e.g. in a bug report:
- Keep only the lines you need (
grep '^\[nautilus:debug:'finds them). - Run a secret scan over them, and replace session ids, signatures, nonces and JWTs with
<redacted>. A session id lets anyone read the presentation through the node's passthrough until the verifier forgets it. Remove raw signed transactions too: anyone can broadcast one that was never mined. And remove presented VP/VC JWTs: they carry the holder's personal data (name, address, registration numbers, whatever the credential states). - Never share
secretsoutput. If you captured any, rotate every credential it printed: the RPC URL's token, the S3 keys, the IPFS JWT. - Delete the files when you are done.