Set package.json to 2.1.0 to match the CHANGELOG entry for the upgrade
tripwire (a [BREAKING] change warrants a minor bump). The startup
tripwire reads package.json as the source of truth, so this is the
version the gate will enforce.
bump-version.yml previously ran `pnpm version patch` on every push to
main, which would patch a deliberate 2.1.0 up to 2.1.1. It now skips the
auto-bump when the pushed commits already changed package.json
themselves. fetch-depth: 0 so the before/after diff has both tips.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The startup tripwire message was written for a coding agent and gave a
human no direction — only the bare `set` override (which skips the
migrations the gate guards). Add one human-addressed stanza pointing to
/update-nanoclaw as the correct fix. The tested CODING AGENT block is
left byte-for-byte unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Refuse to start unless this install reached the current version through a
sanctioned path (setup / update / migrate). A raw `git pull` that skips
migrations now fails loudly with a self-healing message instead of
silently breaking.
- src/upgrade-state.ts: marker at data/upgrade-state.json, getCodeVersion,
isUpgradeCurrent, enforceUpgradeTripwire (fails closed on missing /
corrupt / mismatched marker)
- src/index.ts: gate wired in at startup step 0.5, before DB init
- scripts/upgrade-state.ts: get/set CLI (also the override / recovery cmd)
- setup/service.ts, /update-nanoclaw, /migrate-nanoclaw: stamp on success;
update/migrate also self-update their own skill first
- CHANGELOG [BREAKING] entry bridges existing installs via the skills'
breaking-change check
- docs/upgrade-recovery.md: clearing the tripwire
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A bare * in the pre-filled secret_url path doesn't survive (the gateway
URL-encodes everything, so an unencoded * collapses to just /, which only
exact-matches the path /). Leave the path blank instead so the created
secret matches all of huggingface.co, not a single endpoint.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop "(host pattern pre-filled)" and "— no restart needed" from the HF
setup instructions.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The not-signed-in message hardcoded both a local and a hosted OneCLI
dashboard URL because the container can't tell which gateway it's behind.
But the gateway already tells us: a credential-less proxied request comes
back with the right URL in its error body —
- credential_not_found → secret_url (pre-filled "new secret" form)
- access_restricted → manage_url (grant this agent access)
- app_not_connected → connect_url
Capture whoami's body + status (drop -f so the JSON survives the 401),
extract that URL, and present it. It's always the correct gateway, local
or hosted, with zero extra wiring. The secret_url's pre-filled `path`
defaults to the failing request path (/api/whoami-v2), so broaden it to
/* — otherwise the created secret wouldn't cover the upload endpoints.
Falls back to generic text when there's no gateway JSON to read.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The default OneCLI secret mode for auto-created agents is `all`, not
`selective` — a fresh agent created via ensureAgent({name, identifier})
comes back with secretMode "all", so matching vault secrets inject
automatically. Drop the now-unnecessary per-agent assignment step.
- upload-trace.ts: remove step 3 (set-secret-mode) from the not-authed
message; creating the token and adding it to the vault is enough
- CLAUDE.md: trim the secret-mode gotcha to reflect `all` as the default
- init-onecli skill: replace stale `onecli start` (gone in 1.4.x) and the
`ps aux | grep onecli` check with the real Docker Compose start path
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a runner-handled /upload-trace slash command (admin-gated, like /clear)
that uploads the current session's Claude Code transcript to the user's own
private {hf_user}/nanoclaw-traces dataset, browsable in the HF Agent Trace
Viewer. The transcript is already in the format the viewer auto-detects, so
the command just locates the newest one and pushes it via the Hub commit API.
Auth is handled by the OneCLI gateway: curl goes out through the injected
HTTPS_PROXY, which adds the user's HF token — no credential ever touches
agent code. A missing/unassigned token yields a clear setup message.
- container/agent-runner/src/upload-trace.ts: isUploadTraceCommand() + uploadTrace()
- poll-loop.ts: recognize and handle /upload-trace in the runner
- command-gate.ts: admin-gate /upload-trace on the host
- upload-trace.test.ts: unit + integration coverage for the command
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
claude-code CLI 2.1.128 -> 2.1.154 (Dockerfile build-arg). agent-runner SDK 0.2.128 -> 0.3.154: the 0.3 major moved @anthropic-ai/sdk and @modelcontextprotocol/sdk from regular deps to peer deps, so add @anthropic-ai/sdk ^0.100.0 as a direct dep and raise @modelcontextprotocol/sdk to ^1.29.0. Regenerate bun.lock. Typecheck + agent-runner tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The agent-runner runs the Agent SDK with settingSources: ['project', 'user'], which omits 'local'. Per the SDK docs the 'local' source is what loads CLAUDE.local.md (the 'project' source loads CLAUDE.md). So every group's CLAUDE.local.md is silently never read, even though container/CLAUDE.md tells each agent to use it as per-group memory.
Closes#2185.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The follow-up poll catches and logs SQLite errors but never recovers
from them. On Docker Desktop macOS, the kernel page cache for the
inbound.db bind mount can latch a torn snapshot mid-host-write (a known
virtiofs / gRPC-FUSE coherency issue), after which every fresh
openInboundDb() in the same process sees the same broken view and
emits 'database disk image is malformed' at the poll rate (2/sec).
Reopening the DB handle inside the container does not recover — only
a fresh container mount does. The fix: after CORRUPTION_STREAK_EXIT
consecutive corruption errors (~5s), log a clear message and
process.exit(75) so host-sweep respawns the container with a fresh
mount. Transient single torn reads are still tolerated.
- Add isCorruptionError() helper covering the three SQLite read-side
corruption symptoms (disk image malformed, SQLITE_CORRUPT, file is
not a database).
- Add streak counter scoped to processQuery's pollHandle so it resets
on any successful or non-corruption error.
- Add unit tests for the matcher.
Refs the cross-mount invariants documented in db/connection.ts:11-18.
fe2e881b (#2556) removed the <messages> wrapper from formatChatMessages
so the Claude Agent SDK calls the API instead of emitting a synthetic
stub, but poll-loop.test.ts still asserted the wrapper. The test has
failed on every PR built against main since. Assert the current shape:
no envelope, one self-contained <message> block per message.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CLAUDE_TRANSCRIPT_ROTATE_AGE_DAYS=0 (or negative) is documented to
disable age-based rotation, but transcriptRotateAgeMs() routed it
into the same branch as an unset var and returned the 14-day default.
Sessions intentionally configured to stay long-lived were still
rotated at 14 days, causing unexpected resets and context loss.
Distinguish unset/non-numeric (default 14d) from an explicit
non-positive override (Infinity = disabled; size alone governs).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>