NanoClaw

NanoClaw’s macOS updater can report success without running

Two NanoClaw reports find symlinked macOS temp paths can make the documented updater exit 0 without entering main and can reject state it wrote itself. A second compatibility gap lets Node 22.13 pass setup before better-sqlite3 crashes on open.

← Back to homeOriginal source ↗

nanocoai/nanoclaw issue #3498 is the inspected primary source: “update-nanoclaw: controller exits 0 without running on macOS; symlinked tmpdir defeats the entrypoint guard and hasSafeStatePaths.” The related records below were inspected as supporting context rather than independent confirmation.

The facts

- nanocoai/nanoclaw issue #3498 was created 2026-08-24T00:26:36Z and was open at inspection; its title is “update-nanoclaw: controller exits 0 without running on macOS; symlinked tmpdir defeats the entrypoint guard and hasSafeStatePaths.” - nanocoai/nanoclaw issue #3497 was created 2026-08-24T00:26:30Z and was open at inspection; its title is “setup: better-sqlite3 13 segfaults on open on MacOS, needs Node >=22.14.0.” - nanocoai/nanoclaw PR #3496 was created 2026-08-23T21:48:35Z and was closed at inspection; its title is “versions: repin to hardened-2026-08-23 and let benign lock drift through.” - The repository reported 30608 stars, 962 open issues, default branch main, and last push 2026-08-23T22:01:57Z. - The latest tagged-release baseline checked was v2.2.0 published 2026-08-13T20:43:27Z.

What changed

nanocoai/nanoclaw issue #3498 reports: Summary Two path comparisons in the update controller use path.resolve() where a realpath is needed. On macOS os.tmpdir() and mktemp -d return /var/folders/..., a symlink to /private/var/folders/..., so both comparisons fail. One makes the documented invocation a no-op; the other fails four e2e tests. 1. Entrypoint guard — documented flow does nothing scripts/update-nanoclaw.ts, final lines: the module-entry guard compares import.meta.url against pathToFileURL(path.resolve(process.argv[1])).href. import.meta.url is realpath-resolved by the loader; path.resolve is not..claude/skills/update-nanoclaw/SKILL.md step 1 tells the caller to git archive the controller into $(mktemp -d) and run it from there. On macOS the guard therefore never matches: main() is not called, the process exits **0 with no output**, and no transaction is created. A caller seeing a success code and no output has nothing to distinguish this from a completed step. Confirmed by printing both sides under tsx from a mktemp -d path: import.meta.url is file:///private/var/folders/..., path.resolve(argv[1]) is file:///var/folders/.... Suggested fix: compare realpaths (fs.realpathSync on the resolved argv path, guarded since it throws on a missing path), or drop the guard — the file has no importers outside its own tests. 2. hasSafeStatePaths — rejects state it just wrote scripts/update/transaction.ts, hasSafeStatePaths (~line 110) compares state.projectRoot / transactionRoot / stageRoot via path.resolve. scripts/update/transaction.e2e.test.ts builds its fixture with fs.mkdtempSync(path.join(os.tmpdir(),...)), so one side carries /var/... and the other /private/var/..., and loadState throws

nanocoai/nanoclaw issue #3497 reports: Summary better-sqlite3@13.0.3 segfaults inside new Database() on Node 22 releases older than **22.14.0**. The declared floor is >=22, so an affected Node passes every check and then leaves the install with no working database layer — pnpm test cannot complete and the host cannot start. Where package.json → engines.node is >=22. setup/install-node.sh (~lines 14-15) derives NODE_MAJOR with cut -d. -f1 and compares -ge 22, so the minor is never considered. The CHANGELOG's Node 22 breaking entry says to verify node --version reports "v22 or newer", which an affected version satisfies. Evidence Upstream: WiseLibs/better-sqlite3#1514. A better-sqlite3 contributor narrows it in that thread: the prebuilds are fine on Node 22, but "something has changed since one of the commits contained in v22.14.0, as any version after this release does not cause a segfault". The crash is in napi_module_register_by_symbol at addon load, and is reported on win32-x64 too, so it is not macOS-specific. On darwin-arm64 with a clean v2.2.0 checkout, changing only Node: Node **22.13.1** — node -e 'new (require("better-sqlite3"))(":memory:")' exits 139 with nothing on stdout or stderr. require() alone succeeds; the crash is on open. Full suite: 4 files failed, 89 passed, and 69 files never reported, each ending in [vitest-pool]: Timeout terminating forks worker — all of them SQLite-backed. Node **22.23.2** — same checkout, nothing else changed: 162 files passed, 2050

nanocoai/nanoclaw PR #3496 reports: Stopgap while a release can't be cut.** Gets operators the patched image instead of a dead setup. The breakage New hardened installs have failed setup since **2026-08-21 22:21 +0300** (ea1dadd8). container/pull.sh compares the image's dev.nanoclaw.agent-runner-lock-sha256 label against the checkout's container/agent-runner/bun.lock and refused to retag on any difference: Every published hardened tag — **all nine**, hardened-2026-07-30 through hardened-2026-08-23 — carries 5f499545…, the v2.2.0 lockfile. So no value of the pin clears the refusal, and there is no override for a *mismatched* label (NANOCLAW_ALLOW_UNLABELED_IMAGE only covers a *missing* one). The only escapes were a local build or a release. Why letting this drift through is safe The check is a coarse proxy — it fires on any lockfile change, including one that cannot break a spawn. This drift is that kind: ea1dadd8 bumped @anthropic-ai/claude-agent-sdk ^0.3.197 → ^0.3.238 **and nothing else** — three files, no source changes. The lockfile **adds and removes no package.** Every changed line is a version string or integrity hash for the SDK and its eight platform binaries. Peer ranges (@anthropic-ai/sdk >=0.93.0, @modelcontextprotocol/sdk ^1.29.0, zod ^4.0.0) are identical on both sides. The runner's entire SDK surface is one **unchanged** import: query, HookCallback, PreCompactHookInput — byte-identical at v2.2.0 and at main. So the baked /app/node_modules at 0.3.197 resolves everything the mounted /app/src imports. What changes | File | Change |

Why it matters

An updater that no-ops successfully and a version floor that admits a known crash both corrupt the operator’s evidence before any application code runs. The operator test is whether the system remains bounded and its receipts still describe the action after failure, retry or restart.

Current

The primary record was open when captured on 2026-08-24. The tagged-release baseline was v2.2.0 published 2026-08-13T20:43:27Z. Repository metadata, full source bodies, current pull requests and issues, releases, Google News, Hacker News, Lobsters, Metamesh and the rendered ClawCharts row were inspected. ClawCharts selected the subject; it did not prove the claim.

Evidence

The primary URL is https://github.com/nanocoai/nanoclaw/issues/3498. Supporting records are https://github.com/nanocoai/nanoclaw/issues/3497 and https://github.com/nanocoai/nanoclaw/pull/3496. Source bodies, timestamps and states are preserved in the daily evidence bundle. Test counts and reproductions remain attributed to their authors unless identified as independently rerun.

Source boundary

Open work is described as open, closed work as closed, and operator reports as reports. A pull request is evidence of proposed or reviewed direction, not proof of a shipped release. Search residue, package mirrors and historically published source spines were excluded.

Operator take

Normalize path comparisons through realpath, raise the Node floor to 22.14.0, and add a database-open smoke so setup fails with a named cause. Preserve a before-state receipt, make the smallest reversible change, and verify the original failure independently.

Caveat

Public project records are mutable. Status, scope and evidence can change after publication. This brief records the inspected state and does not authorize changes to a reader’s deployment.

Source inspected; source state, environment and release boundary remain explicit.