fix canary daemon, UDT, and fork reliability#457
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe CLI now centralizes async execution and JSON results, strengthens devnet readiness and fork isolation, supports safer private-key inputs, updates UDT validation and issuance results, improves daemon lifecycle handling, and enforces ckb-tui checksum verification. Tests and documentation cover the new behavior. ChangesCanary DevRel fixes
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/tools/ckb-tui.ts (1)
221-244: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy liftSecurity Misconfiguration (CWE-345)
Reachability: External
● Entry src/cmd/status.ts:37 CKBTui │ ▼ ● Sink src/tools/ckb-tui.tsDo not trust
checksums-sha256.txtfetched from the same release for arbitrary versions.v0.1.3is pinned viaKNOWN_SHA256, but the fallback path still accepts a manifest that can be replaced together with the archive if that release is compromised. Keep non-default versions pinned here, or verify a signed manifest with an embedded publisher key.🤖 Prompt for 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. In `@src/tools/ckb-tui.ts` around lines 221 - 244, Update the checksum verification flow around checksumUrl and fetchResult so arbitrary non-default versions cannot trust checksums-sha256.txt fetched from the same release. Reuse the existing KNOWN_SHA256 pinning for every supported non-default version, or require verification of the manifest with an embedded publisher key before accepting expectedHash; preserve assertChecksum only after the checksum source is independently trusted.
🤖 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/cmd/deposit.ts`:
- Around line 30-39: Update the Testnet early-return path in the deposit flow to
capture the hash returned by depositFromTestnetFaucet(), ensure that helper
returns its transferAll transaction hash, and emit logger.result with the same
deposit result shape before returning the hash. Preserve the existing mainnet
transfer reporting and return behavior.
In `@src/cmd/devnet-info.ts`:
- Around line 35-36: Update the readiness logging around indexerLag so positive
lag values use logger.warn instead of logger.info, while retaining logger.info
for zero or non-positive values and the existing null handling.
In `@src/cmd/node.ts`:
- Around line 130-138: Re-check the ckbExited state after createRPCProxy and
before proxy.start() in the miner startup flow. If CKB has already exited, stop
or skip starting the proxy and preserve the existing shutdown/error behavior;
otherwise continue starting the proxy normally.
In `@src/cmd/transfer.ts`:
- Around line 21-28: Update the UDT option handling around udtKind, udtTypeArgs,
and ckb.transfer() so either --udt-kind or --udt-type-args explicitly selects
UDT processing. Require non-empty type arguments, including rejecting an empty
string, before validating and proceeding; only use the CKB transfer path when
neither UDT option is provided.
- Around line 31-33: Update the transfer flow around warnIfMainnetForkSigning
and CKB construction to fail closed on Mainnet forks: require an explicit
replay-risk override when signing with a non-built-in private key, and reject
any selected input created before the fork point. Ensure these validations occur
before transaction construction or signing, while preserving the existing
warning and indexer checks where applicable.
In `@src/util/logger.ts`:
- Around line 72-82: Update the UnifiedLogger constructor initialization to call
setJsonMode(this.jsonMode) immediately after createLogger(), ensuring JSON-mode
Console transports configure stderrLevels before any logs are emitted.
---
Outside diff comments:
In `@src/tools/ckb-tui.ts`:
- Around line 221-244: Update the checksum verification flow around checksumUrl
and fetchResult so arbitrary non-default versions cannot trust
checksums-sha256.txt fetched from the same release. Reuse the existing
KNOWN_SHA256 pinning for every supported non-default version, or require
verification of the manifest with an embedded publisher key before accepting
expectedHash; preserve assertChecksum only after the checksum source is
independently trusted.
🪄 Autofix (Beta)
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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 85afcabf-c1bf-47ef-925b-fa8dfebfce74
📒 Files selected for processing (46)
.changeset/clean-pandas-report.mdREADME.mdsrc/cli.tssrc/cmd/accounts.tssrc/cmd/balance.tssrc/cmd/clean.tssrc/cmd/config.tssrc/cmd/create.tssrc/cmd/debug.tssrc/cmd/deploy.tssrc/cmd/deposit.tssrc/cmd/devnet-config.tssrc/cmd/devnet-fork.tssrc/cmd/devnet-info.tssrc/cmd/node.tssrc/cmd/status.tssrc/cmd/transfer-all.tssrc/cmd/transfer.tssrc/cmd/udt.tssrc/devnet/fork.tssrc/devnet/readiness.tssrc/node/init-chain.tssrc/node/install.tssrc/sdk/ckb.tssrc/tools/ckb-tui.tssrc/util/fork-safety.tssrc/util/fs.tssrc/util/logger.tssrc/util/private-key.tssrc/util/validator.tstests/accounts.test.tstests/ckb-tui-checksum.test.tstests/devnet-config-command.test.tstests/devnet-fork.test.tstests/fork-safety.test.tstests/init-chain.test.tstests/logger.test.tstests/node-command.test.tstests/node-supervisor.test.tstests/private-key.test.tstests/readiness-warning.test.tstests/readiness.test.tstests/sdk/ckb.udt.test.tstests/status.test.tstests/udt.test.tstests/validator.test.ts
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
src/cmd/deposit.ts (1)
32-38: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winOther (CWE-693)
Reachability: External
● Entry src/cli.ts:158 deposit │ ▼ ● Sink src/cmd/deposit.tsEnforce the fork boundary before depositing.
This only logs a replay-risk warning. Unlike
transfer, it never callsvalidateMainnetForkSigningor passesrejectInputsAtOrBeforeBlocktoCKB.transfer, so a Mainnet-fork deposit can sign copied inputs without the required origin check.Proposed fix
- warnIfMainnetForkSigning(network, privateKey); + const rejectInputsAtOrBeforeBlock = validateMainnetForkSigning(network, privateKey); await warnIfForkIndexerIsBehind(network); const txHash = await ckb.transfer({ toAddress, privateKey, amountInCKB, + rejectInputsAtOrBeforeBlock, });🤖 Prompt for 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. In `@src/cmd/deposit.ts` around lines 32 - 38, Update the deposit flow around warnIfMainnetForkSigning and ckb.transfer to enforce the fork boundary, not just warn: validate Mainnet-fork signing with validateMainnetForkSigning and pass the resulting rejectInputsAtOrBeforeBlock constraint into CKB.transfer, matching the existing transfer implementation.src/cmd/node.ts (3)
411-428: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy liftMake daemon startup check-and-claim atomic.
Two concurrent
startDaemon()calls can both pass the node/PID checks, then spawn competing daemons and overwrite or race on the PID file. Use an OS-level startup lock or atomically reserve the control file before spawning.🤖 Prompt for 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. In `@src/cmd/node.ts` around lines 411 - 428, Update startDaemon to atomically acquire an OS-level startup lock or reserve the daemon control/PID file before spawning the process, so concurrent calls cannot both pass the readiness and existing-PID checks. Ensure the reservation is created exclusively, reject startup when another invocation owns it, and release or clean it up on startup failure and daemon shutdown while preserving the existing duplicate-daemon errors.
490-522: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winKeep the PID file until the daemon exits.
terminateProcess()only sendsSIGTERMhere, socleanupPidFile()can run while the child is still shutting down. Wait for exit first, and only remove the PID file once termination is confirmed.🤖 Prompt for 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. In `@src/cmd/node.ts` around lines 490 - 522, Update the failed-readiness cleanup around terminateProcess and cleanupPidFile to wait until the child process has exited after sending SIGTERM, then remove the PID file only after termination is confirmed. Preserve the existing handling for an already-exited child and the readiness error propagation.
542-564: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winDon't treat
EPERMas a stale PID insrc/cmd/node.ts.isProcessAlive()currently turns anyprocess.kill(pid, 0)error intofalse, so a live daemon you can’t signal gets its PID file deleted and reported asstale-pid. Only mapESRCHto stale; letEPERMsurface as permission denied.🤖 Prompt for 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. In `@src/cmd/node.ts` around lines 542 - 564, Update isProcessAlive to distinguish process.kill(pid, 0) errors: return false only for ESRCH, while allowing EPERM and other errors to propagate as permission or operational failures. Preserve the existing stale-pid cleanup and result behavior in the node stop flow only when the process is confirmed absent.
🧹 Nitpick comments (1)
tests/node-supervisor.test.ts (1)
114-126: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAssert the fork-boundary write happens before miner spawn. This test can still pass if miner startup moves ahead of
markForkFirstRunComplete; add an ordering assertion between those calls.🤖 Prompt for 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. In `@tests/node-supervisor.test.ts` around lines 114 - 126, Update the test around nodeDevnet to assert that mockMarkForkFirstRunComplete is invoked before mockProxyStart, using invocation ordering metadata or an equivalent call-order assertion while preserving the existing argument and startup expectations.
🤖 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/cmd/deposit.ts`:
- Around line 25-27: Update the result object in the deposit command after
depositFromTestnetFaucet so it reports the requested amount under
requestedAmount, removes amountInCKB from the amount field, and identifies the
result as the fixed faucet claim instead. Keep the existing txHash, network, and
toAddress fields unchanged.
In `@src/util/fork-safety.ts`:
- Around line 43-46: Update the fork boundary parsing in the shown try block to
validate that the BigInt result is non-negative before returning it. Treat
negative values as invalid and route them through the existing error handling,
while preserving valid non-negative boundary behavior.
---
Outside diff comments:
In `@src/cmd/deposit.ts`:
- Around line 32-38: Update the deposit flow around warnIfMainnetForkSigning and
ckb.transfer to enforce the fork boundary, not just warn: validate Mainnet-fork
signing with validateMainnetForkSigning and pass the resulting
rejectInputsAtOrBeforeBlock constraint into CKB.transfer, matching the existing
transfer implementation.
In `@src/cmd/node.ts`:
- Around line 411-428: Update startDaemon to atomically acquire an OS-level
startup lock or reserve the daemon control/PID file before spawning the process,
so concurrent calls cannot both pass the readiness and existing-PID checks.
Ensure the reservation is created exclusively, reject startup when another
invocation owns it, and release or clean it up on startup failure and daemon
shutdown while preserving the existing duplicate-daemon errors.
- Around line 490-522: Update the failed-readiness cleanup around
terminateProcess and cleanupPidFile to wait until the child process has exited
after sending SIGTERM, then remove the PID file only after termination is
confirmed. Preserve the existing handling for an already-exited child and the
readiness error propagation.
- Around line 542-564: Update isProcessAlive to distinguish process.kill(pid, 0)
errors: return false only for ESRCH, while allowing EPERM and other errors to
propagate as permission or operational failures. Preserve the existing stale-pid
cleanup and result behavior in the node stop flow only when the process is
confirmed absent.
---
Nitpick comments:
In `@tests/node-supervisor.test.ts`:
- Around line 114-126: Update the test around nodeDevnet to assert that
mockMarkForkFirstRunComplete is invoked before mockProxyStart, using invocation
ordering metadata or an equivalent call-order assertion while preserving the
existing argument and startup expectations.
🪄 Autofix (Beta)
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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: f82d4af5-df9a-4131-9ea9-7a2f964a862d
📒 Files selected for processing (20)
README.mdsrc/cli.tssrc/cmd/deposit.tssrc/cmd/devnet-info.tssrc/cmd/node.tssrc/cmd/transfer.tssrc/devnet/fork.tssrc/sdk/ckb.tssrc/tools/ckb-tui.tssrc/util/fork-safety.tssrc/util/logger.tstests/ckb-tui-checksum.test.tstests/deposit.test.tstests/devnet-fork.test.tstests/devnet-info.test.tstests/fork-safety.test.tstests/logger.test.tstests/node-supervisor.test.tstests/sdk/ckb.udt.test.tstests/udt.test.ts
🚧 Files skipped from review as they are similar to previous changes (6)
- tests/ckb-tui-checksum.test.ts
- src/cmd/devnet-info.ts
- src/cmd/transfer.ts
- README.md
- src/util/logger.ts
- src/cli.ts
There was a problem hiding this comment.
🧹 Nitpick comments (2)
tests/node-command.test.ts (1)
237-256: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAssert cleanup happens after termination is confirmed.
This test only proves both events occurred; it passes if the PID file is removed before the liveness retries. Record the liveness count when
mockUnlinkSyncruns and assert it is at least 3.Proposed test strengthening
let livenessChecks = 0; + let livenessChecksWhenPidFileRemoved: number | undefined; + mockUnlinkSync.mockImplementation((file: string) => { + if (file === pidFile) livenessChecksWhenPidFileRemoved = livenessChecks; + }); mockWaitForNodeReady.mockResolvedValueOnce({ ready: false, error: 'proxy unavailable' }); killSpy.mockImplementation((_pid: number, signal?: NodeJS.Signals | number) => { ... expect(killSpy).toHaveBeenCalledWith(-12345, 'SIGTERM'); expect(livenessChecks).toBeGreaterThanOrEqual(3); expect(mockUnlinkSync).toHaveBeenCalledWith(pidFile); + expect(livenessChecksWhenPidFileRemoved).toBeGreaterThanOrEqual(3);🤖 Prompt for 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. In `@tests/node-command.test.ts` around lines 237 - 256, Strengthen the test around startNode by recording livenessChecks when mockUnlinkSync executes, then assert that recorded count is at least 3 after the rejected startup. Keep the existing SIGTERM, retry-count, and PID-file assertions, ensuring cleanup is verified only after termination confirmation.src/cmd/node.ts (1)
554-572: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winEscalate to SIGKILL when the failed child won't respond to SIGTERM.
Unlike
stopNode(which escalates to SIGKILL after a 5s SIGTERM wait), this readiness-failure path only sends SIGTERM once. If the child ignores it (hung/stuck CKB process),exitedstaysfalse, the PID file is deliberately preserved, and the process is left running indefinitely — the caller must manually runoffckb node stopto fully clean it up. MirroringstopNode's SIGTERM→wait→SIGKILL pattern here would make startup-failure cleanup self-sufficient and consistent with the stop path.♻️ Proposed fix
if (!readiness.ready) { let exited = !isProcessAlive(child.pid); try { if (!exited) { await terminateProcess(child.pid, 'SIGTERM'); exited = await waitForProcessExit(child.pid, 5000); + if (!exited) { + await terminateProcess(child.pid, 'SIGKILL'); + exited = await waitForProcessExit(child.pid, 5000); + } } } catch { // The failed child may already have exited. exited = !isProcessAlive(child.pid); }🤖 Prompt for 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. In `@src/cmd/node.ts` around lines 554 - 572, Update the readiness-failure cleanup around terminateProcess and waitForProcessExit to mirror stopNode’s escalation flow: send SIGTERM, wait up to 5 seconds, then send SIGKILL and wait again if the child remains alive. Set exited from the final wait result so the existing PID-file cleanup and error reporting correctly reflect whether the process was terminated.
🤖 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.
Nitpick comments:
In `@src/cmd/node.ts`:
- Around line 554-572: Update the readiness-failure cleanup around
terminateProcess and waitForProcessExit to mirror stopNode’s escalation flow:
send SIGTERM, wait up to 5 seconds, then send SIGKILL and wait again if the
child remains alive. Set exited from the final wait result so the existing
PID-file cleanup and error reporting correctly reflect whether the process was
terminated.
In `@tests/node-command.test.ts`:
- Around line 237-256: Strengthen the test around startNode by recording
livenessChecks when mockUnlinkSync executes, then assert that recorded count is
at least 3 after the rejected startup. Keep the existing SIGTERM, retry-count,
and PID-file assertions, ensuring cleanup is verified only after termination
confirmation.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: a13a6d89-07d0-46d6-a762-738d245a4868
📒 Files selected for processing (7)
src/cmd/deposit.tssrc/cmd/node.tssrc/util/fork-safety.tstests/deposit.test.tstests/fork-safety.test.tstests/node-command.test.tstests/node-supervisor.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- tests/deposit.test.ts
- tests/fork-safety.test.ts
- src/util/fork-safety.ts
- tests/node-supervisor.test.ts
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
src/cmd/node.ts (4)
471-486: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winVerify daemon identity before treating a live PID as an existing daemon.
A crashed daemon’s PID can be reused by an unrelated process. This branch then refuses every new startup even though no CKB daemon is running. Use
verifyDaemonIdentity(existing.pid, existing)before rejecting; if it fails, remove or quarantine only the stale metadata without signaling that process.🤖 Prompt for 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. In `@src/cmd/node.ts` around lines 471 - 486, Update the existing-PID handling around readPidFile and isProcessAlive to call verifyDaemonIdentity(existing.pid, existing) before rejecting a live PID. Only throw the duplicate-daemon errors when the identity check succeeds; otherwise treat the PID metadata as stale, clean up or quarantine the PID file, and never signal or affect the unrelated process.
542-553: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winClean up the detached child on every post-spawn startup failure.
If
writePidFile,readForkState, orwaitForNodeReadythrows, execution bypasses the!readiness.readycleanup and leaves the detached daemon orphaned with stale PID metadata. Wrap the entire post-spawn readiness phase in a cleanup path that terminates the child, waits/escalates, and removes the PID file before rethrowing.🤖 Prompt for 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. In `@src/cmd/node.ts` around lines 542 - 553, Wrap the entire post-spawn readiness phase in the relevant node startup function, including writePidFile, readForkState, and waitForNodeReady, with error cleanup. On any thrown error, terminate the detached child using the existing wait/escalation behavior, remove the PID file, and rethrow the original error; preserve the existing !readiness.ready cleanup path without duplicating cleanup.
578-591: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winKeep JSON mode to one stdout result.
The success and info logs run before
logger.result; in JSON mode they can emit additional records, violating the one-structured-result contract. Suppress these human-readable logs in JSON mode or route them to stderr, leavinglogger.resultas the only stdout record.🤖 Prompt for 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. In `@src/cmd/node.ts` around lines 578 - 591, The devnet daemon startup path emits human-readable success and info logs before logger.result, breaking JSON mode’s single stdout result contract. Update the logging around the visible logger.success and logger.info calls to suppress them in JSON mode or route them to stderr, while keeping logger.result as the only stdout record.
536-542: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winKeep the PID status as
startinguntil readiness succeeds.The daemon is marked
runningimmediately after spawn, before CKB, miner, and proxy readiness completes. A concurrentstopor secondstartcan therefore bypass the explicitstartinghandling. Writestatus: 'running'only after the readiness check passes.🤖 Prompt for 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. In `@src/cmd/node.ts` around lines 536 - 542, Update the metadata creation in the node startup flow to write status "starting" after spawning the child, and defer changing it to "running" until the CKB, miner, and proxy readiness check succeeds. Ensure the persisted PID metadata is updated after readiness so concurrent stop and start operations continue to recognize the daemon as starting beforehand.
🤖 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.
Outside diff comments:
In `@src/cmd/node.ts`:
- Around line 471-486: Update the existing-PID handling around readPidFile and
isProcessAlive to call verifyDaemonIdentity(existing.pid, existing) before
rejecting a live PID. Only throw the duplicate-daemon errors when the identity
check succeeds; otherwise treat the PID metadata as stale, clean up or
quarantine the PID file, and never signal or affect the unrelated process.
- Around line 542-553: Wrap the entire post-spawn readiness phase in the
relevant node startup function, including writePidFile, readForkState, and
waitForNodeReady, with error cleanup. On any thrown error, terminate the
detached child using the existing wait/escalation behavior, remove the PID file,
and rethrow the original error; preserve the existing !readiness.ready cleanup
path without duplicating cleanup.
- Around line 578-591: The devnet daemon startup path emits human-readable
success and info logs before logger.result, breaking JSON mode’s single stdout
result contract. Update the logging around the visible logger.success and
logger.info calls to suppress them in JSON mode or route them to stderr, while
keeping logger.result as the only stdout record.
- Around line 536-542: Update the metadata creation in the node startup flow to
write status "starting" after spawning the child, and defer changing it to
"running" until the CKB, miner, and proxy readiness check succeeds. Ensure the
persisted PID metadata is updated after readiness so concurrent stop and start
operations continue to recognize the daemon as starting beforehand.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: a704f7e6-c4f0-4744-89f4-0f04af249805
📒 Files selected for processing (2)
src/cmd/node.tstests/node-command.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/node-command.test.ts
* fix canary daemon, UDT, and fork reliability (#457) * fix canary devrel reliability issues * fix fork copy isolation on windows * remove implicit fork source discovery * address PR review feedback * address follow-up review feedback * fix daemon test on Windows runners * complete failed daemon cleanup * harden daemon startup recovery * chore(release): bump version to 0.4.9 (#458) --------- Co-authored-by: humble-little-bear <retric@ckba.build>
Summary
offckb devnet info, Indexer lag warnings, fork-aware address prefixes/balances, hidden-by-default private keys, and Mainnet replay-risk banners--jsonposition-independent with one result object on stdout, NDJSON logs/errors on stderr, stable non-zero failures, no stacks, and EPIPE handling--privkey-file/OFFCKB_PRIVATE_KEYsupportValidation
npm test -- --runInBand: 24 suites passed, 181 tests passed, 7 skippednpm run lint: no errors (4 pre-existingno-explicit-anywarnings)npm run build: passedget_peersreturned[]Fixes #456