From f68f6da406fe7637ef4b764fe58d86bcf8cb2da8 Mon Sep 17 00:00:00 2001 From: Alex Mashkovtsev Date: Mon, 4 May 2026 16:49:53 +0800 Subject: [PATCH 1/4] fix(agent-runner): derive MCP allowedTools from registered mcpServers Claude Code 2.1.116+ treats SDK `allowedTools` as a hard whitelist: servers whose namespace isnt listed are filtered out before the agent ever sees them, regardless of `permissionMode: bypassPermissions` or any `permissions.allow` in settings. The static TOOL_ALLOWLIST only contained `mcp__nanoclaw__*`, so any MCP wired via add_mcp_server (or directly in container.json) was silently dropped. Derive `mcp____*` entries at the SDK call site from the already-aggregated `this.mcpServers` map, mirroring the SDKs own sanitization rule (chars outside [A-Za-z0-9_-] become _). Prior diagnosis by @jsboige in #2028 (withdrawn, not upstreamed). --- .../agent-runner/src/providers/claude.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/container/agent-runner/src/providers/claude.ts b/container/agent-runner/src/providers/claude.ts index c9478b8..6c30cc2 100644 --- a/container/agent-runner/src/providers/claude.ts +++ b/container/agent-runner/src/providers/claude.ts @@ -34,7 +34,11 @@ const SDK_DISALLOWED_TOOLS = [ 'ExitWorktree', ]; -// Tool allowlist for NanoClaw agent containers +// Tool allowlist for NanoClaw agent containers. MCP-tool entries are derived +// at the call site from the registered `mcpServers` map so that any server +// added via `add_mcp_server` (or wired in container.json directly) is +// reachable to the agent — without this, the SDK's allowedTools filter +// silently drops every MCP namespace not listed here. const TOOL_ALLOWLIST = [ 'Bash', 'Read', @@ -54,9 +58,15 @@ const TOOL_ALLOWLIST = [ 'ToolSearch', 'Skill', 'NotebookEdit', - 'mcp__nanoclaw__*', ]; +// MCP server names are sanitized by the SDK when forming tool prefixes: +// any character outside [A-Za-z0-9_-] becomes '_'. Mirror that here so our +// allowlist patterns match what the SDK actually exposes. +function mcpAllowPattern(serverName: string): string { + return `mcp__${serverName.replace(/[^a-zA-Z0-9_-]/g, '_')}__*`; +} + interface SDKUserMessage { type: 'user'; message: { role: 'user'; content: string }; @@ -277,7 +287,10 @@ export class ClaudeProvider implements AgentProvider { resume: input.continuation, pathToClaudeCodeExecutable: '/pnpm/claude', systemPrompt: instructions ? { type: 'preset' as const, preset: 'claude_code' as const, append: instructions } : undefined, - allowedTools: TOOL_ALLOWLIST, + allowedTools: [ + ...TOOL_ALLOWLIST, + ...Object.keys(this.mcpServers).map(mcpAllowPattern), + ], disallowedTools: SDK_DISALLOWED_TOOLS, env: this.env, permissionMode: 'bypassPermissions', From b33f6654fdece99c5b11e06fc160917f1ac6fb61 Mon Sep 17 00:00:00 2001 From: "exe.dev user" Date: Mon, 4 May 2026 09:23:43 +0000 Subject: [PATCH 2/4] fix(setup): use fmtDuration in the container-build spinner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit setup/lib/windowed-runner.ts was the one place on main still printing elapsed time as raw seconds (`(170s)`) instead of using the minute-aware `fmtDuration` helper from #2108. Two spots — the live spinner suffix that ticks during the build, and the success/error completion suffix — both now go through `fmtDuration`, so anything past 60 seconds renders as `Xm Ys` (e.g. `2m 50s`) like the rest of the setup flow. The miss happened because a separate PR (closed) was supposed to remove the timer entirely from this file, so #2108 deliberately skipped it. With that other PR closed, applying `fmtDuration` here is the consistent fix. Pure formatting change. The helper itself is unchanged from #2108; behavior under 60s is identical (`Xs`); behavior past 60s now matches everywhere else. --- setup/lib/windowed-runner.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/setup/lib/windowed-runner.ts b/setup/lib/windowed-runner.ts index 6f165a4..87c971e 100644 --- a/setup/lib/windowed-runner.ts +++ b/setup/lib/windowed-runner.ts @@ -23,7 +23,7 @@ import { emit as phEmit } from './diagnostics.js'; import type { StepResult, SpinnerLabels } from './runner.js'; import { dumpTranscriptOnFailure, spawnStep, writeStepEntry } from './runner.js'; import * as setupLog from '../logs.js'; -import { brandBody, fitToWidth } from './theme.js'; +import { brandBody, fitToWidth, fmtDuration } from './theme.js'; const WINDOW_SIZE = 3; const SPINNER_FRAMES = ['◒', '◐', '◓', '◑']; @@ -85,9 +85,8 @@ async function runUnderWindow( const redraw = (): void => { if (stallPromptActive) return; out.write(`\x1b[${WINDOW_SIZE + 1}A`); - const elapsed = Math.round((Date.now() - start) / 1000); const icon = SPINNER_FRAMES[frameIdx % SPINNER_FRAMES.length]; - const suffix = ` (${elapsed}s)`; + const suffix = ` (${fmtDuration(Date.now() - start)})`; const header = fitToWidth(labels.running, suffix); out.write(`\x1b[2K${k.cyan(icon)} ${header}${k.dim(suffix)}\n`); @@ -164,8 +163,7 @@ async function runUnderWindow( out.write(SHOW_CURSOR); process.off('exit', restoreCursorOnExit); - const elapsed = Math.round((Date.now() - start) / 1000); - const suffix = ` (${elapsed}s)`; + const suffix = ` (${fmtDuration(Date.now() - start)})`; if (result.ok) { const isSkipped = result.terminal?.fields.STATUS === 'skipped'; const msg = isSkipped && labels.skipped ? labels.skipped : labels.done; From 251b31cd7847bf4c1faf610b1f1c954d64931852 Mon Sep 17 00:00:00 2001 From: koshkoshinsk Date: Mon, 4 May 2026 14:27:07 +0000 Subject: [PATCH 3/4] feat(setup): warn when running on a Google Compute Engine VM NanoClaw is known to not run reliably on GCE instances. Detect via DMI during pre-flight (between the spec check and root warning) and let the user abort before sinking time into bootstrap. Co-Authored-By: Claude Opus 4.7 (1M context) --- nanoclaw.sh | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/nanoclaw.sh b/nanoclaw.sh index c2b2614..c17966e 100755 --- a/nanoclaw.sh +++ b/nanoclaw.sh @@ -200,6 +200,33 @@ if [ "$LOW_MEM" = true ] || [ "$LOW_DISK" = true ]; then esac fi +# ─── pre-flight: Google Cloud VM warning (Linux) ────────────────────── +# NanoClaw is known to not run reliably on Google Compute Engine instances. +# Warn early — before the root check or bootstrap spinner — so users can +# switch providers before sinking time into setup. Detection uses DMI +# (no network round-trip), which on GCE reports "Google" / "Google +# Compute Engine". +if [ "$(uname -s)" = "Linux" ] \ + && { grep -qi 'Google' /sys/class/dmi/id/product_name 2>/dev/null \ + || grep -qi 'Google' /sys/class/dmi/id/sys_vendor 2>/dev/null; }; then + printf ' %s\n' "$(red 'Warning: Google Cloud VM detected.')" + printf ' %s\n' "$(dim 'Google blocks sudo commands, so NanoClaw is unlikely to run successfully on this VM.')" + printf ' %s\n\n' "$(dim 'If you want to run NanoClaw successfully, switch to a different provider (Hetzner, Hostinger, exe.dev and others..).')" + read -r -p " $(bold 'Try anyway?') [y/N] " GCE_ANS Date: Mon, 4 May 2026 15:31:09 +0000 Subject: [PATCH 4/4] chore: bump version to 2.0.29 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f305bec..032c7f8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nanoclaw", - "version": "2.0.28", + "version": "2.0.29", "description": "Personal Claude assistant. Lightweight, secure, customizable.", "type": "module", "packageManager": "pnpm@10.33.0",