Summary
After a successful contract call on a local devnet, offckb logs script --tail 20 returns no output and exits with code 0, even though the node's run.log already contains the contract's debug messages.
The script-log filter expects the target ckb-script, but the tested CKB versions emit these messages under ckb_script::verify. As a result, OffCKB filters out the actual contract output.
Environment
@offckb/cli: 0.4.13, npm release; verified with offckb --version.
- CKB: reproduced with
0.207.0 and 0.208.0. The latter is the default CKB version configured by this OffCKB release; both runs explicitly selected a local binary with --binary-path.
- Node.js:
20.17.0.
- OS: macOS
26.6.2, arm64.
- Local devnet running in daemon mode, with isolated OffCKB directories.
- The bundled logger configuration was unchanged:
filter = "info,ckb-script=debug".
Reproduction
-
Start a devnet and create a TypeScript project:
offckb node --daemon --binary-path /path/to/ckb
offckb create log-demo --no-interactive --language typescript \
--manager pnpm --contract-name diagnostic-contract --no-install --no-git
cd log-demo
-
Make contracts/diagnostic-contract/src/index.ts emit identifiable debug messages:
import * as bindings from '@ckb-js-std/bindings';
bindings.debug('OFFCKB_DIAGNOSTIC_FIRST');
bindings.debug('OFFCKB_DIAGNOSTIC_SECOND');
bindings.exit(0);
-
Build and deploy it using a funded devnet signer:
pnpm install
pnpm run build
pnpm run deploy --network devnet --yes
-
Submit a transaction that executes the deployed contract through the OffCKB RPC proxy and wait until get_transaction reports committed.
In the reproduced run, a public CCC SDK client signed a transaction using the JS VM as an output type script, referencing the deployed bytecode and generated system-script dependencies. The transaction was submitted to http://127.0.0.1:28114 with send_transaction(transaction, "passthrough").
-
After the messages appear in <devnet.dataPath>/logs/run.log, run:
offckb logs script --tail 20
devnet.dataPath is available through offckb config list.
Actual Result
The command returns empty stdout and stderr, with exit code 0.
The node log already contains the contract output. One actual entry from the CKB 0.208.0 run, which used a unique marker, is:
2026-09-11 11:20:02.169 +00:00 GlobalRt-10 DEBUG ckb_script::verify script group: Byte32(0xafa75b18ff86fe7e188169be16b3279833ceeb197b0d14abf23ff2b005199f8d) DEBUG OUTPUT: OFFCKB_DIAGNOSTIC_19f360119a504c10af02b4d395832def_FIRST
The entire log contained only 78 lines, so this was not caused by the script-log scan window being too small. The transaction had committed, and the command was invoked while the daemon was still running.
The node, miner and RPC log targets returned their corresponding real log records; filtering RPC logs by this transaction's hash also worked.
Expected Result
offckb logs script should display the contract's debug messages from the running devnet, while excluding unrelated node records. Users should be able to find their contract output without locating and inspecting OffCKB's internal log files.
Initial Analysis
In the source corresponding to this release:
The actual records have target ckb_script::verify and contain DEBUG OUTPUT:, so they fail that exact-match filter and are discarded.
The configured ckb-script=debug filter still enables these records: in both tested CKB versions, convert_compatible_crate_name() keeps the original filter directive and adds its underscore variant, ckb_script=debug. The logger then writes and forwards the original record.target(), without renaming it. This explains why the messages reach run.log with target ckb_script::verify, even though the configuration uses ckb-script.
The reproduced failure above covers reading existing script logs while the daemon is running. The --follow and live console-forwarding paths have the same filtering problem according to source inspection, but were not independently exercised in this run.
Suggested Fix
Update both src/cmd/logs.ts and src/cmd/node.ts to use a shared script-debug record classifier. Apply it consistently to historical log reading, --follow, and live console forwarding, including the file-filtering helper used by historical reads.
Match the actual ckb_script module path and its submodules, preserving the module boundary:
target === 'ckb_script' ||
target.startsWith('ckb_script::')
A bare target.startsWith('ckb_script') also matches unrelated names such as ckb_script_other. For ckb_script module records, additionally recognize the contract-debug message marker DEBUG OUTPUT: so unrelated CKB verification logs are not presented as contract output. Checking only level === 'DEBUG' does not establish that a message came from a contract. Preserve continuation lines for multiline contract output.
Regression fixtures should use captured real records, including ckb_script::verify and script group: Byte32(0x...) DEBUG OUTPUT: .... Tests using only the synthetic ckb-script / script group: 0xabcd format cannot detect this mismatch. Include unrelated verification records as negative examples.
Retain a regression test that invokes a real contract and checks its unique debug messages, and validate all three paths after the fix:
offckb logs script, including typical --tail and --grep usage.
offckb logs script --follow when the contract emits new messages.
- Live contract-output forwarding in the foreground
offckb node console.
Impact
The basic contract-log troubleshooting workflow is unusable in the tested environments: a successful command with empty output makes existing contract diagnostics invisible to the user.
Source inspection also indicates that the foreground offckb node console's CKB-Script: live output is likely affected by the same exact-match condition. This additional impact still needs independent runtime verification; both command paths should be fixed together.
Summary
After a successful contract call on a local devnet,
offckb logs script --tail 20returns no output and exits with code0, even though the node'srun.logalready contains the contract's debug messages.The script-log filter expects the target
ckb-script, but the tested CKB versions emit these messages underckb_script::verify. As a result, OffCKB filters out the actual contract output.Environment
@offckb/cli:0.4.13, npm release; verified withoffckb --version.0.207.0and0.208.0. The latter is the default CKB version configured by this OffCKB release; both runs explicitly selected a local binary with--binary-path.20.17.0.26.6.2, arm64.filter = "info,ckb-script=debug".Reproduction
Start a devnet and create a TypeScript project:
offckb node --daemon --binary-path /path/to/ckb offckb create log-demo --no-interactive --language typescript \ --manager pnpm --contract-name diagnostic-contract --no-install --no-git cd log-demoMake
contracts/diagnostic-contract/src/index.tsemit identifiable debug messages:Build and deploy it using a funded devnet signer:
Submit a transaction that executes the deployed contract through the OffCKB RPC proxy and wait until
get_transactionreportscommitted.In the reproduced run, a public CCC SDK client signed a transaction using the JS VM as an output type script, referencing the deployed bytecode and generated system-script dependencies. The transaction was submitted to
http://127.0.0.1:28114withsend_transaction(transaction, "passthrough").After the messages appear in
<devnet.dataPath>/logs/run.log, run:devnet.dataPathis available throughoffckb config list.Actual Result
The command returns empty stdout and stderr, with exit code
0.The node log already contains the contract output. One actual entry from the CKB
0.208.0run, which used a unique marker, is:The entire log contained only 78 lines, so this was not caused by the script-log scan window being too small. The transaction had committed, and the command was invoked while the daemon was still running.
The node, miner and RPC log targets returned their corresponding real log records; filtering RPC logs by this transaction's hash also worked.
Expected Result
offckb logs scriptshould display the contract's debug messages from the running devnet, while excluding unrelated node records. Users should be able to find their contract output without locating and inspecting OffCKB's internal log files.Initial Analysis
In the source corresponding to this release:
SCRIPT_LOG_TARGETis set tockb-script.filterLinesByTarget()requires an exact target match.showLogs()applies this filter before taking the script tail.--followpath inlogs.tsuses the same exact target comparison.node.tsalso requires that exact target before forwarding script output to the console.The actual records have target
ckb_script::verifyand containDEBUG OUTPUT:, so they fail that exact-match filter and are discarded.The configured
ckb-script=debugfilter still enables these records: in both tested CKB versions,convert_compatible_crate_name()keeps the original filter directive and adds its underscore variant,ckb_script=debug. The logger then writes and forwards the originalrecord.target(), without renaming it. This explains why the messages reachrun.logwith targetckb_script::verify, even though the configuration usesckb-script.The reproduced failure above covers reading existing script logs while the daemon is running. The
--followand live console-forwarding paths have the same filtering problem according to source inspection, but were not independently exercised in this run.Suggested Fix
Update both
src/cmd/logs.tsandsrc/cmd/node.tsto use a shared script-debug record classifier. Apply it consistently to historical log reading,--follow, and live console forwarding, including the file-filtering helper used by historical reads.Match the actual
ckb_scriptmodule path and its submodules, preserving the module boundary:A bare
target.startsWith('ckb_script')also matches unrelated names such asckb_script_other. Forckb_scriptmodule records, additionally recognize the contract-debug message markerDEBUG OUTPUT:so unrelated CKB verification logs are not presented as contract output. Checking onlylevel === 'DEBUG'does not establish that a message came from a contract. Preserve continuation lines for multiline contract output.Regression fixtures should use captured real records, including
ckb_script::verifyandscript group: Byte32(0x...) DEBUG OUTPUT: .... Tests using only the syntheticckb-script/script group: 0xabcdformat cannot detect this mismatch. Include unrelated verification records as negative examples.Retain a regression test that invokes a real contract and checks its unique debug messages, and validate all three paths after the fix:
offckb logs script, including typical--tailand--grepusage.offckb logs script --followwhen the contract emits new messages.offckb nodeconsole.Impact
The basic contract-log troubleshooting workflow is unusable in the tested environments: a successful command with empty output makes existing contract diagnostics invisible to the user.
Source inspection also indicates that the foreground
offckb nodeconsole'sCKB-Script:live output is likely affected by the same exact-match condition. This additional impact still needs independent runtime verification; both command paths should be fixed together.