github nanocoai/nanoclaw v2.4.0

2 hours ago

NanoClaw 2.4.0 adds credential gateways installed through skills (OneCLI stays the default, Iron Proxy is new), community-portal setup for Echo's hardened image and a managed Slack app, install-wide and per-group model and speed controls, a Mattermost channel, and a reworked OpenCode provider. Agents now receive all of their capability instructions, replies stay in the thread they answer, and host restarts and /update-nanoclaw are more reliable. The default Claude model moves to Opus 5.5 and new Codex threads to gpt-6-astra, so check the section below before you update.

⚠️ Before you update

  • [BREAKING] Custom source that composes agent instructions must move to the new module. CLAUDE.md was a list of @ imports into /app, and Claude Code silently drops imports that resolve outside the project directory, so eight of nine instruction sections never reached the model; it is now one flat file with every source inlined, shared with the Codex provider. src/claude-md-compose.ts is now src/project-doc-compose.ts, composeGroupClaudeMd(group) became composeGroupProjectDoc(group, groupDir, spec), and the /app/CLAUDE.md and /workspace/agent/.claude-fragments mounts are gone. Migration: run grep -rn --exclude='*.test.ts' "claude-md-compose\|composeGroupClaudeMd\|claude-fragments" src/ setup/ scripts/; no hits means nothing to do, otherwise repoint the import and pass DEFAULT_PROJECT_DOC as the third argument. Then clear the inert leftovers once with rm -rf groups/*/.claude-fragments groups/*/.claude-shared.md. Standard installations need no source edits; see docs/release-2.4-update.md.
  • [BREAKING] Forks that merge upstream by hand must install their credential gateway before restarting. Credential gateways now install through skills: /add-onecli supplies OneCLI and /add-iron-proxy adds optional Iron Proxy support, with a shared contract owning session lifecycle and human approvals. OneCLI remains the simple-setup default, gateway selection appears only in advanced setup, and upgrades preserve the existing selection. Setting NANOCLAW_GATEWAY_PROVIDER alone does not install its implementation, and the host refuses to start without a registered gateway. Migration: /update-nanoclaw materializes the selected gateway before cutover; a manual merge must apply /add-onecli (or the selected gateway's skill) before restarting. See gateway migration for detection, verification, and rollback.
  • Claude groups with no model set move to Opus 5.5, which changes cost and usage-limit burn. API-key installs move from Opus 5, and Pro and Team Standard installs from Sonnet. To keep a model, set NANOCLAW_DEFAULT_MODEL in .env or run ncl groups config update --id <group> --model <model> before updating.
  • Codex groups with no model set start new threads on gpt-6-astra. /add-codex now pins @openai/codex 0.155.1, whose default replaces 0.146.0's gpt-5.6-sol; existing threads keep the model they started on. To stay on the old model, run ncl groups config update --id <group-id> --model gpt-5.6-sol, then ncl groups restart --id <group-id>. NANOCLAW_DEFAULT_MODEL would also hold it but applies to Claude groups too. Existing Codex installs get the new pin and the provider fix below together from /update-nanoclaw, which rebuilds the agent image on installs that build their own.
  • Claude agents default to a concise tone. A Claude group whose settings do not set an output style now starts with Concise; the default is written once into the group's Claude settings and an explicit value is never overwritten, so groups that already set outputStyle are unaffected. To keep or change the tone, set outputStyle in data/v2-sessions/<agent-group-id>/.claude-shared/settings.json (the group's /home/node/.claude/settings.json); project and local settings still take precedence.
  • Existing OpenCode installs must refresh the skill payload. The optional /add-opencode skill now uses the native provider contracts and the new contract refuses the old payload: re-run /add-opencode, rebuild the agent image, and restart the host and the OpenCode groups.
  • Cutover stops this install's agent containers itself. /update-nanoclaw no longer times out while an idle agent container is running: it stops the containers with a 10-second grace inside a one-minute bound and names them as it goes, so no manual docker stop is needed. An agent in the middle of a turn loses that turn, as with ncl groups restart.

✨ New

  • Iron Proxy is an alternative local gateway. /add-iron-proxy installs Iron Proxy and its Iron Control web console (Secrets, Credentials, OAuth Apps, Principals) on Docker; each request passes front identity and allowlist, then human approval, then stored credentials, before it reaches upstream. Codex works through Iron, including its WebSocket turns, setup can install Codex and pair it under Iron in one run, and a failed credential save names its cause on screen and in logs/setup.log. See .claude/skills/add-iron-proxy/SKILL.md.
  • OpenCode can authenticate through Iron Control. It can store API keys and native ChatGPT OAuth credentials there, including token refresh and reauthentication that preserves grants.
  • Echo's hardened image and a managed Slack app are set up through the community portal with one browser sign-in. Setup prints one link that covers sign-in, terminal approval and the perk choice, and the install token never passes through the browser. The running host stays linked to your account's cell, so a perk changed in the browser reaches the running agent and a Slack install approved days later finishes on its own. Setup asks each portal question once, including across a resumed or repeated run; Dial and Tavily are not offered. See docs/community-portal.md.
  • Two install-wide model knobs. NANOCLAW_DEFAULT_MODEL fills in the model for agent groups that have not set one of their own (a group's own model always wins), and NANOCLAW_FAST_MODE=1 turns on the API's fast serving tier for every agent, with faster output at a higher per-token price. Both are read from the host .env when container.json is materialized, so a change takes effect at the next container start with no host restart. Installs that set neither are unaffected: neither field is written to container.json and the provider sends exactly the options it sent before.
  • Per-group serving speed. ncl groups config update --id <group> --speed fast selects a serving tier the group's provider declares; Claude accepts standard and fast, fast mode costs more per token, and --speed "" clears it. Agent callers need approval, as with --timezone.
  • Agents run on Claude Code 2.1.280. A cd now carries over between turns, Monitor watches expire after 30 minutes, resumed agents keep seeing their current name and destinations, and claude.ai account skills and plugins are not synced into agent containers.
  • The OpenCode provider is reworked. /add-opencode now uses host-managed authentication and model selection, native tools and compaction, and reliable cancellation, and setup can install and authenticate it.
  • Mattermost is a channel. /add-mattermost installs it, can connect to a server you already run, validates and atomically saves its settings, verifies the running bot and server callback, and no longer needs a separately installed jq.
  • Host restarts keep approvals and delivery state. Approvals, delivery retry counts and session claims now live in the central database instead of host memory: an approval card sent before a restart still resolves after it (a late approve that can no longer be applied gets a follow-up card), overdue approvals expire as "no response" instead of being swept as "host restarted", and a message that keeps failing gives up after its retry limit even across a crash loop. A second host started in the same checkout no longer takes over a running host's ncl socket, and a host still draining during a restart keeps its sessions until it exits.
  • ncl status shows the running host process and its connected channel adapters. Setup uses it to confirm that the restarted host is the one it started.
  • The Slack agents companion skills ship on main. /slack-a2a-rooms and /slack-agent-flow now come with the core source.

🛠️ Fixes

  • Codex turns no longer complete with nothing sent. From 0.147.0, Codex starts a turn after about one second without the tools of any MCP server still starting. The provider now waits for every MCP server to finish starting (Codex gives up on one after 30 seconds, as 0.146.0 did) and requires NanoClaw's own tool server: if that server cannot start, the message fails with an error instead of running without its tools.
  • Replies stay in the thread they answer. Replies, send_message, send_file, ask_user_question and send_card land in the thread of the message being answered, not the main channel or a thread that spoke more recently; this covers shared and agent-shared sessions, and replies from a turn longer than 30 minutes stay threaded.
  • Busy installs stay fast. The host sweep and delivery polls visit up to 8 sessions at a time at a fixed rate, and cross-session ambient context fans out only to a conversation's 8 most recently active threads (within 3 days) after the container wakes, so installs with many sessions or busy channels no longer see reply and task latency grow with size.
  • Quoted .env values read the same everywhere. Setup had its own parser that did not strip surrounding quotes, so a hand-edited TZ="America/New_York" or NANOCLAW_TEMPLATE_PATH="/opt/my templates" reached the wizard with the quotes attached and the template path was bridged into process.env unusable; both readers now share the host parser (envValue in src/env.ts, the single-key form of readEnvFile).
  • /update-nanoclaw loads its controller again. It crashed with MODULE_NOT_FOUND; it now also snapshots symlinked data roots by content and is correct on macOS hosts.
  • Setup is sturdier on Linux. No hang with a system-wide Node, a user-owned npm prefix instead of an invisible sudo, a nohup-started host verified and kept running when user systemd is missing, installers that run under /bin/sh and /bin/bash, a uvx-installed pnpm recovered, and the replacement host verified after a restart.
  • Concurrent SQLite migrations no longer fail setup or upgrade. Failed registry copies keep existing files, registry skills install from single-branch clones, and installing missing skill files keeps your customizations.
  • OneCLI certificate and credential-stub mounts survive host restarts and temp cleanup, and WEBHOOK_PORT is read from .env.
  • Conversations reach the right place and stop when asked. Cancelled questions stop promptly, failed provider turns keep partial replies, scheduled-task escalations default to the agent's own channel, and Slack group-DM and multi-instance approval cards name the right conversation and bot.
  • Task failures are easier to diagnose. ncl tasks update rejects an empty --prompt, and a timed-out pre-task script says so.
  • /add-dial handles more starting states. It installs the Dial tool on a first-ever install (before any agent exists), echoes what you type at its agent-scope question, and /add-dial-tool can re-register a rotated key with OneCLI.
  • Agents know their card options. They are told that send_card supports URL link buttons and that interactive choices need ask_user_question.

🔒 Security

  • Setup redacts resolved secrets in skill logs and commits related .env values atomically.
  • The agent image is repinned and the supply-chain gate is enforced. The image is hardened-2026-08-24 and runs Bun 1.4.0, and pnpm's 3-day minimumReleaseAge gate now takes effect. See docs/hardened-image.md.

🔧 For custom installations

  • Chat SDK channels can recover content the adapter left in the raw payload. The bridge drops message.raw before persisting, so anything a platform did not project into its message text was lost; Slack keeps pasted tables in attachments[].blocks[], and an agent saw only the sentence before the table. The optional extractRawText hook on ChatSdkBridgeConfig lets a channel rescue that content as text before raw is discarded; raw payloads still never reach the database, and a channel without the hook is unaffected. Slack uses it to recover pasted tables: refresh /add-slack to get it.
  • Custom OpenCode models can declare their limits. /add-opencode documents OPENCODE_MODEL_CONTEXT_LIMIT, OPENCODE_MODEL_OUTPUT_LIMIT and OPENCODE_MODEL_INPUT_MODALITIES, which set a custom model's context, output and input limits.

New Contributors

Contributors

Thanks to everyone who landed work in this release:

  • versions: repin to hardened-2026-08-23 and let benign lock drift through by @gavrielc in #3496
  • versions: repin the agent image to hardened-2026-08-24 by @amit-shafnir in #3531
  • docs(add-opencode): document the OPENCODE_MODEL_* limit and modality env vars by @glifocat in #3524
  • ci: handle branches without an old image pin by @glifocat in #3534
  • fix(dial): post-merge follow-ups — credential re-run, step captions, registry CI by @glifocat in #3432
  • feat(channels): add Mattermost installation skill by @glifocat in #3507
  • fix(compose): inline every instruction source into one project document by @amit-shafnir in #3536
  • Fix the blind agent-scope prompt by @OmriBenShoham in #3525
  • fix(mattermost): improve initial setup and SiteURL handling by @glifocat in #3557
  • fix(update): make the transaction controller correct on macOS hosts by @chiptoe-svg in #3506
  • Install the Dial tool when the install has no agents yet by @OmriBenShoham in #3578
  • fix(add-dial): add dial-status.test.ts to the nc:copy list by @OmriBenShoham in #3582
  • fix(approvals): MPDM-aware approval cards via resolveConversation seam by @Koshkoshinsk in #3385
  • fix(approvals): preserve adapter instance for DMs by @Koshkoshinsk in #3387
  • fix(agent-runner): default task-run escalations to the agent's own channel by @Koshkoshinsk in #3388
  • rollup: the durable host — coordination state, wake seam, reconcile queue, restart-honest delivery, claim fencing (#3508#3528) by @gavrielc in #3653
  • feat(db): durable host-coordination state and the seams that will consume it by @gavrielc in #3508
  • fix(env): read quoted .env values the same way everywhere by @gavrielc in #3659
  • fix(task-script): say a pre-task script timed out instead of "Command failed" by @gavrielc in #3662
  • chore: use a neutral placeholder name in examples and fixtures by @gavrielc in #3663
  • feat(config): install-wide default model and fast serving tier by @gavrielc in #3664
  • feat(channels): let a chat-sdk channel recover content left in message.raw by @gavrielc in #3665
  • fix(add-slack): copy slack-raw-text with the adapter that imports it by @gavrielc in #3667
  • fix(ncl tasks): reject an empty --prompt on update by @tchopoorian in #3655
  • test(skill-directives): expect the slack-raw-text pair in add-slack's copy fence by @glifocat in #3682
  • chore(github): add issue forms by @glifocat in #3644
  • docs(contributing): add the issue-side intake section by @glifocat in #3651
  • ci(labels): automatic area/* from changed paths and kind/* from PR type by @glifocat in #3647
  • feat(release): harvest PR release-note blocks into a draft changelog by @glifocat in #3650
  • ci(labels): PR template v2 with token parsing and managed-kind reconcile by @glifocat in #3648
  • ci(labels): report-only template-compliance status with a single fix comment by @glifocat in #3657
  • feat(skills): the Slack agents companion skills move in-tree — main is canonical by @gavrielc in #3695
  • chore(container): bump Bun and Claude runtimes by @omri-maya in #3698
  • fix(supply-chain): activate the minimumReleaseAge gate (hoist out of pnpm: key) by @sturdy4days in #2973
  • fix(agent-runner): tell the agent send_card drops callback actions by @glifocat in #3427
  • refactor(providers): render provider instructions from core-owned canon by @zvi-fried in #3591
  • refactor(providers): declare the runtime provider contract by @zvi-fried in #3581
  • refactor(providers): declare the host provider contract by @zvi-fried in #3585
  • refactor(providers): render provider instructions from core-owned canon by @zvi-fried in #3727
  • feat(groups): add a core-owned speed inference property by @zvi-fried in #3592
  • refactor(providers): declare the setup provider contract and install verifier by @zvi-fried in #3586
  • ci: add a gate job and a post-merge run on main by @gavrielc in #3736
  • fix(db): stop the nested-continuation conformance test from racing its own watchdog by @gavrielc in #3737
  • ci(registry-skills): a registry gate check the ruleset can require, and a build that survives a Docker Hub 5xx by @gavrielc in #3739
  • feat(setup): connect the host to its community cell and manage perks in the browser by @gavrielc in #3729
  • fix(agent-runner): thread replies from the message being answered by @zvi-fried in #3738
  • fix(setup): verify sees a nohup-started host when systemd has no user instance by @glifocat in #3760
  • fix: recover uvx-installed pnpm after bootstrap by @tahodev in #3771
  • fix(setup): print one portal link for the not-enrolled browser handoff by @Koshkoshinsk in #3754
  • fix(setup): skip portal reminders the operator already answered by @Koshkoshinsk in #3758
  • fix(setup): fetch explicit registry tracking refs by @glifocat in #3773
  • fix(db): recheck migrations under SQLite write lock by @glifocat in #3766
  • fix(setup): preserve files when registry copies fail by @glifocat in #3767
  • fix(webhook): honor WEBHOOK_PORT from .env by @glifocat in #3770
  • fix(setup): run downloaded installers with the system shell by absolute path by @glifocat in #3776
  • fix(setup): start and verify the Linux fallback service by @glifocat in #3768
  • fix: persist OneCLI gateway files across restarts by @glifocat in #3774
  • fix(add-opencode): drop the pre-cli-tools Dockerfile guard on refresh and remove by @glifocat in #3763
  • gateway providers: pass the session's container name in the provider input by @gavrielc in #3782
  • fix(setup): restore the agent provider picker for fresh installs by @gavrielc in #3790
  • fix: preserve provider cancellation, failure delivery, and skill files by @glifocat in #3746
  • feat(add-opencode): implement provider contracts and host authentication by @glifocat in #3733
  • feat(add-opencode): integrate setup and host assistance by @glifocat in #3747
  • fix(setup): redact resolved secrets in skill execution logs by @glifocat in #3805
  • fix(setup): commit related environment values atomically by @glifocat in #3806
  • fix(mattermost): parse setup responses without jq by @glifocat in #3809
  • fix(mattermost): connect to an operator-managed server by @glifocat in #3777
  • fix(mattermost): validate and atomically persist selected settings by @glifocat in #3778
  • fix(setup): verify the replacement host after restarting by @glifocat in #3779
  • fix(mattermost): verify the running bot and server callback by @glifocat in #3780
  • fix(update): snapshot symlinked mutable roots by @linhongyu510 in #3689
  • Ignore .worktrees/ by @amit-shafnir in #3822
  • feat(providers): declare default tone settings by @zvi-fried in #3826
  • perf(cross-session-context): bound echo fan to the hot set, off the wake path by @gavrielc in #3829
  • test(webhook): allocate free ports from the kernel instead of picking them at random by @gavrielc in #3830
  • perf(host): reconcile and drain sessions concurrently; fixed-rate delivery polls by @gavrielc in #3832
  • ci(registry-skills): bound the skill test jobs at 20 minutes by @glifocat in #3836
  • fix(setup): enable corepack pnpm in ~/.local/bin when the global bin dir is read-only by @glifocat in #3847
  • fix(setup): replace broken sudo retry with user-owned npm prefix fallback by @DorZvulun in #3844
  • fix(setup): remember the image-source answer across resume so the Echo perk is offered once by @glifocat in #3861
  • chore(add-codex): pin @openai/codex 0.155.1 by @amit-shafnir in #3867
  • chore(container): bump Claude Code to 2.1.280 and the Agent SDK to 0.3.280 by @amit-shafnir in #3868
  • fix(update): extract the whole scripts/ tree for the update controller by @tchopoorian in #3750
  • refactor(gateway): centralize the credential gateway contract by @zvi-fried in #3815
  • refactor(gateway): extract OneCLI into an installable skill by @zvi-fried in #3816
  • feat(skills): add the Iron Proxy gateway by @zvi-fried in #3817
  • feat(opencode): support authentication through Iron Proxy by @glifocat in #3825
  • fix(update): stop this install's containers at cutover instead of waiting for them by @glifocat in #3873
  • chore(release): v2.4.0 by @glifocat in #3877

Full Changelog: v2.3.0...v2.4.0

Don't miss a new nanoclaw release

NewReleases is sending notifications on new releases.