From deb5389077534db268bf2ee900502c61fd57c66b Mon Sep 17 00:00:00 2001 From: Ken Bolton Date: Wed, 25 Mar 2026 16:52:29 -0400 Subject: [PATCH 1/8] fix(skill/channel-formatting): correct Telegram link behaviour in SKILL.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Telegram Markdown v1 renders [text](url) links natively — they are now preserved rather than flattened to "text (url)". Update the skill table to reflect the actual post-fix behaviour. Co-Authored-By: Claude Sonnet 4.6 --- .claude/skills/channel-formatting/SKILL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.claude/skills/channel-formatting/SKILL.md b/.claude/skills/channel-formatting/SKILL.md index b995fb8..3e2334c 100644 --- a/.claude/skills/channel-formatting/SKILL.md +++ b/.claude/skills/channel-formatting/SKILL.md @@ -11,8 +11,8 @@ Telegram. | Channel | Transformation | |---------|---------------| -| WhatsApp | `**bold**` → `*bold*`, `*italic*` → `_italic_`, headings → bold, links flattened | -| Telegram | same as WhatsApp | +| WhatsApp | `**bold**` → `*bold*`, `*italic*` → `_italic_`, headings → bold, links → `text (url)` | +| Telegram | same as WhatsApp, but `[text](url)` links are preserved (Markdown v1 renders them natively) | | Slack | same as WhatsApp, but links become `` | | Discord | passthrough (Discord already renders Markdown) | | Signal | passthrough for `parseTextStyles`; `parseSignalStyles` in `src/text-styles.ts` produces plain text + native `textStyle` ranges for use by the Signal skill | From 8f01a9a05ee6e3adb61566afd5a57e2e014eaff6 Mon Sep 17 00:00:00 2001 From: gavrielc Date: Fri, 27 Mar 2026 14:24:41 +0300 Subject: [PATCH 2/8] chore: remove unused dependencies (yaml, zod, @vitest/coverage-v8) None of these are imported or referenced by the main codebase. yaml had zero imports; zod is only used in container/agent-runner (which has its own package.json); coverage-v8 was never configured. Co-Authored-By: Claude Opus 4.6 (1M context) --- package-lock.json | 222 +--------------------------------------------- package.json | 5 +- 2 files changed, 5 insertions(+), 222 deletions(-) diff --git a/package-lock.json b/package-lock.json index 46a8742..cf59cbb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,15 +12,12 @@ "better-sqlite3": "11.10.0", "cron-parser": "5.5.0", "pino": "^9.6.0", - "pino-pretty": "^13.0.0", - "yaml": "^2.8.2", - "zod": "^4.3.6" + "pino-pretty": "^13.0.0" }, "devDependencies": { "@eslint/js": "^9.35.0", "@types/better-sqlite3": "^7.6.12", "@types/node": "^22.10.0", - "@vitest/coverage-v8": "^4.0.18", "eslint": "^9.35.0", "eslint-plugin-no-catch-all": "^1.1.0", "globals": "^15.12.0", @@ -35,66 +32,6 @@ "node": ">=20" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", - "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.3", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", @@ -743,16 +680,6 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -760,17 +687,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, "node_modules/@onecli-sh/sdk": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@onecli-sh/sdk/-/sdk-0.2.0.tgz", @@ -1460,37 +1376,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@vitest/coverage-v8": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.18.tgz", - "integrity": "sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.0.18", - "ast-v8-to-istanbul": "^0.3.10", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.2.0", - "magicast": "^0.5.1", - "obug": "^2.1.1", - "std-env": "^3.10.0", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@vitest/browser": "4.0.18", - "vitest": "4.0.18" - }, - "peerDependenciesMeta": { - "@vitest/browser": { - "optional": true - } - } - }, "node_modules/@vitest/expect": { "version": "4.0.18", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", @@ -1670,18 +1555,6 @@ "node": ">=12" } }, - "node_modules/ast-v8-to-istanbul": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.11.tgz", - "integrity": "sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.31", - "estree-walker": "^3.0.3", - "js-tokens": "^10.0.0" - } - }, "node_modules/atomic-sleep": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", @@ -2380,13 +2253,6 @@ "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", "license": "MIT" }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, "node_modules/husky": { "version": "9.1.7", "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", @@ -2496,45 +2362,6 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/joycon": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", @@ -2544,13 +2371,6 @@ "node": ">=10" } }, - "node_modules/js-tokens": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", - "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", - "dev": true, - "license": "MIT" - }, "node_modules/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", @@ -2643,34 +2463,6 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/magicast": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", - "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "source-map-js": "^1.2.1" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/mimic-response": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", @@ -3801,7 +3593,10 @@ "version": "2.8.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "dev": true, "license": "ISC", + "optional": true, + "peer": true, "bin": { "yaml": "bin.mjs" }, @@ -3823,15 +3618,6 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } - }, - "node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } } } } diff --git a/package.json b/package.json index fca2280..d21ccd0 100644 --- a/package.json +++ b/package.json @@ -25,15 +25,12 @@ "better-sqlite3": "11.10.0", "cron-parser": "5.5.0", "pino": "^9.6.0", - "pino-pretty": "^13.0.0", - "yaml": "^2.8.2", - "zod": "^4.3.6" + "pino-pretty": "^13.0.0" }, "devDependencies": { "@eslint/js": "^9.35.0", "@types/better-sqlite3": "^7.6.12", "@types/node": "^22.10.0", - "@vitest/coverage-v8": "^4.0.18", "eslint": "^9.35.0", "eslint-plugin-no-catch-all": "^1.1.0", "globals": "^15.12.0", From 2f472a8600a3f30d76311aac27c2620dd36981c8 Mon Sep 17 00:00:00 2001 From: gavrielc Date: Fri, 27 Mar 2026 14:31:23 +0300 Subject: [PATCH 3/8] feat: add opt-in model management tools to ollama skill setup Update SKILL.md to ask users during setup whether they want model management tools (pull, delete, show, list-running) and set OLLAMA_ADMIN_TOOLS=true in .env accordingly. Core inference tools remain always available. Incorporates #1456 by @bitcryptic-gw. Closes #1331. Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/skills/add-ollama-tool/SKILL.md | 56 +++++++++++++++++++++---- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/.claude/skills/add-ollama-tool/SKILL.md b/.claude/skills/add-ollama-tool/SKILL.md index a347b49..aa69295 100644 --- a/.claude/skills/add-ollama-tool/SKILL.md +++ b/.claude/skills/add-ollama-tool/SKILL.md @@ -1,15 +1,21 @@ --- name: add-ollama-tool -description: Add Ollama MCP server so the container agent can call local models for cheaper/faster tasks like summarization, translation, or general queries. +description: Add Ollama MCP server so the container agent can call local models and optionally manage the Ollama model library. --- # Add Ollama Integration -This skill adds a stdio-based MCP server that exposes local Ollama models as tools for the container agent. Claude remains the orchestrator but can offload work to local models. +This skill adds a stdio-based MCP server that exposes local Ollama models as tools for the container agent. Claude remains the orchestrator but can offload work to local models, and can optionally manage the model library directly. -Tools added: -- `ollama_list_models` — lists installed Ollama models -- `ollama_generate` — sends a prompt to a specified model and returns the response +Core tools (always available): +- `ollama_list_models` — list installed Ollama models with name, size, and family +- `ollama_generate` — send a prompt to a specified model and return the response + +Management tools (opt-in via `OLLAMA_ADMIN_TOOLS=true`): +- `ollama_pull_model` — pull (download) a model from the Ollama registry +- `ollama_delete_model` — delete a locally installed model to free disk space +- `ollama_show_model` — show model details: modelfile, parameters, and architecture info +- `ollama_list_running` — list models currently loaded in memory with memory usage and processor type ## Phase 1: Pre-flight @@ -89,6 +95,23 @@ Build must be clean before proceeding. ## Phase 3: Configure +### Enable model management tools (optional) + +Ask the user: + +> Would you like the agent to be able to **manage Ollama models** (pull, delete, inspect, list running)? +> +> - **Yes** — adds tools to pull new models, delete old ones, show model info, and check what's loaded in memory +> - **No** — the agent can only list installed models and generate responses (you manage models yourself on the host) + +If the user wants management tools, add to `.env`: + +```bash +OLLAMA_ADMIN_TOOLS=true +``` + +If they decline (or don't answer), do not add the variable — management tools will be disabled by default. + ### Set Ollama host (optional) By default, the MCP server connects to `http://host.docker.internal:11434` (Docker Desktop) with a fallback to `localhost`. To use a custom Ollama host, add to `.env`: @@ -106,7 +129,7 @@ launchctl kickstart -k gui/$(id -u)/com.nanoclaw # macOS ## Phase 4: Verify -### Test via WhatsApp +### Test inference Tell the user: @@ -114,6 +137,14 @@ Tell the user: > > The agent should use `ollama_list_models` to find available models, then `ollama_generate` to get a response. +### Test model management (if enabled) + +If `OLLAMA_ADMIN_TOOLS=true` was set, tell the user: + +> Send a message like: "pull the gemma3:1b model" or "which ollama models are currently loaded in memory?" +> +> The agent should call `ollama_pull_model` or `ollama_list_running` respectively. + ### Monitor activity (optional) Run the watcher script for macOS notifications when Ollama is used: @@ -129,9 +160,10 @@ tail -f logs/nanoclaw.log | grep -i ollama ``` Look for: -- `Agent output: ... Ollama ...` — agent used Ollama successfully -- `[OLLAMA] >>> Generating` — generation started (if log surfacing works) +- `[OLLAMA] >>> Generating` — generation started - `[OLLAMA] <<< Done` — generation completed +- `[OLLAMA] Pulling model:` — pull in progress (management tools) +- `[OLLAMA] Deleted:` — model removed (management tools) ## Troubleshooting @@ -151,3 +183,11 @@ The agent is trying to run `ollama` CLI inside the container instead of using th ### Agent doesn't use Ollama tools The agent may not know about the tools. Try being explicit: "use the ollama_generate tool with gemma3:1b to answer: ..." + +### `ollama_pull_model` times out on large models + +Large models (7B+) can take several minutes. The tool uses `stream: false` so it blocks until complete — this is intentional. For very large pulls, use the host CLI directly: `ollama pull ` + +### Management tools not showing up + +Ensure `OLLAMA_ADMIN_TOOLS=true` is set in `.env` and the service was restarted after adding it. From 7b22e23761cb83eba12e3b7b25bfdf468b3ab692 Mon Sep 17 00:00:00 2001 From: gavrielc Date: Fri, 27 Mar 2026 15:13:00 +0300 Subject: [PATCH 4/8] chore: replace pino/pino-pretty with built-in logger Drop 23 transitive dependencies by replacing pino + pino-pretty with a ~70-line logger that matches the same output format and API. All 80+ call sites work unchanged. Production deps now: @onecli-sh/sdk, better-sqlite3, cron-parser. Co-Authored-By: Claude Opus 4.6 (1M context) --- package-lock.json | 235 +----------------------------------------- package.json | 4 +- src/logger.ts | 73 +++++++++++-- src/mount-security.ts | 8 +- 4 files changed, 70 insertions(+), 250 deletions(-) diff --git a/package-lock.json b/package-lock.json index cf59cbb..7888048 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,9 +10,7 @@ "dependencies": { "@onecli-sh/sdk": "^0.2.0", "better-sqlite3": "11.10.0", - "cron-parser": "5.5.0", - "pino": "^9.6.0", - "pino-pretty": "^13.0.0" + "cron-parser": "5.5.0" }, "devDependencies": { "@eslint/js": "^9.35.0", @@ -695,12 +693,6 @@ "node": ">=20" } }, - "node_modules/@pinojs/redact": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", - "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", - "license": "MIT" - }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.57.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", @@ -1555,15 +1547,6 @@ "node": ">=12" } }, - "node_modules/atomic-sleep": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", - "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1714,12 +1697,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "license": "MIT" - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1752,15 +1729,6 @@ "node": ">= 8" } }, - "node_modules/dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -2072,12 +2040,6 @@ "node": ">=12.0.0" } }, - "node_modules/fast-copy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.2.tgz", - "integrity": "sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==", - "license": "MIT" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2096,12 +2058,6 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "license": "MIT" - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -2247,12 +2203,6 @@ "node": ">=8" } }, - "node_modules/help-me": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", - "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", - "license": "MIT" - }, "node_modules/husky": { "version": "9.1.7", "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", @@ -2362,15 +2312,6 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, - "node_modules/joycon": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", - "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", @@ -2563,15 +2504,6 @@ ], "license": "MIT" }, - "node_modules/on-exit-leak-free": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", - "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -2685,76 +2617,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pino": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", - "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", - "license": "MIT", - "dependencies": { - "@pinojs/redact": "^0.4.0", - "atomic-sleep": "^1.0.0", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^2.0.0", - "pino-std-serializers": "^7.0.0", - "process-warning": "^5.0.0", - "quick-format-unescaped": "^4.0.3", - "real-require": "^0.2.0", - "safe-stable-stringify": "^2.3.1", - "sonic-boom": "^4.0.1", - "thread-stream": "^3.0.0" - }, - "bin": { - "pino": "bin.js" - } - }, - "node_modules/pino-abstract-transport": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", - "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", - "license": "MIT", - "dependencies": { - "split2": "^4.0.0" - } - }, - "node_modules/pino-pretty": { - "version": "13.1.3", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.3.tgz", - "integrity": "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==", - "license": "MIT", - "dependencies": { - "colorette": "^2.0.7", - "dateformat": "^4.6.3", - "fast-copy": "^4.0.0", - "fast-safe-stringify": "^2.1.1", - "help-me": "^5.0.0", - "joycon": "^3.1.1", - "minimist": "^1.2.6", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^3.0.0", - "pump": "^3.0.0", - "secure-json-parse": "^4.0.0", - "sonic-boom": "^4.0.1", - "strip-json-comments": "^5.0.2" - }, - "bin": { - "pino-pretty": "bin.js" - } - }, - "node_modules/pino-pretty/node_modules/pino-abstract-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", - "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", - "license": "MIT", - "dependencies": { - "split2": "^4.0.0" - } - }, - "node_modules/pino-std-serializers": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", - "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", - "license": "MIT" - }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -2835,22 +2697,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/process-warning": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", - "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, "node_modules/pump": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", @@ -2870,12 +2716,6 @@ "node": ">=6" } }, - "node_modules/quick-format-unescaped": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", - "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", - "license": "MIT" - }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -2914,15 +2754,6 @@ "node": ">= 6" } }, - "node_modules/real-require": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", - "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", - "license": "MIT", - "engines": { - "node": ">= 12.13.0" - } - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -3007,31 +2838,6 @@ ], "license": "MIT" }, - "node_modules/safe-stable-stringify": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", - "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/secure-json-parse": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", - "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", @@ -3117,15 +2923,6 @@ "simple-concat": "^1.0.0" } }, - "node_modules/sonic-boom": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", - "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", - "license": "MIT", - "dependencies": { - "atomic-sleep": "^1.0.0" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3136,15 +2933,6 @@ "node": ">=0.10.0" } }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -3168,18 +2956,6 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/strip-json-comments": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", - "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -3221,15 +2997,6 @@ "node": ">=6" } }, - "node_modules/thread-stream": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", - "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", - "license": "MIT", - "dependencies": { - "real-require": "^0.2.0" - } - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", diff --git a/package.json b/package.json index d21ccd0..a86e33a 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,7 @@ "dependencies": { "@onecli-sh/sdk": "^0.2.0", "better-sqlite3": "11.10.0", - "cron-parser": "5.5.0", - "pino": "^9.6.0", - "pino-pretty": "^13.0.0" + "cron-parser": "5.5.0" }, "devDependencies": { "@eslint/js": "^9.35.0", diff --git a/src/logger.ts b/src/logger.ts index 273dc0f..80cba30 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,11 +1,72 @@ -import pino from 'pino'; +const LEVELS = { debug: 20, info: 30, warn: 40, error: 50, fatal: 60 } as const; +type Level = keyof typeof LEVELS; -export const logger = pino({ - level: process.env.LOG_LEVEL || 'info', - transport: { target: 'pino-pretty', options: { colorize: true } }, -}); +const COLORS: Record = { + debug: '\x1b[34m', + info: '\x1b[32m', + warn: '\x1b[33m', + error: '\x1b[31m', + fatal: '\x1b[41m\x1b[37m', +}; +const KEY_COLOR = '\x1b[35m'; +const MSG_COLOR = '\x1b[36m'; +const RESET = '\x1b[39m'; +const FULL_RESET = '\x1b[0m'; -// Route uncaught errors through pino so they get timestamps in stderr +const threshold = + LEVELS[(process.env.LOG_LEVEL as Level) || 'info'] ?? LEVELS.info; + +function formatErr(err: unknown): string { + if (err instanceof Error) { + return `{\n "type": "${err.constructor.name}",\n "message": "${err.message}",\n "stack":\n ${err.stack}\n }`; + } + return JSON.stringify(err); +} + +function formatData(data: Record): string { + let out = ''; + for (const [k, v] of Object.entries(data)) { + if (k === 'err') { + out += `\n ${KEY_COLOR}err${RESET}: ${formatErr(v)}`; + } else { + out += `\n ${KEY_COLOR}${k}${RESET}: ${JSON.stringify(v)}`; + } + } + return out; +} + +function ts(): string { + const d = new Date(); + return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}.${String(d.getMilliseconds()).padStart(3, '0')}`; +} + +function log(level: Level, dataOrMsg: Record | string, msg?: string): void { + if (LEVELS[level] < threshold) return; + const tag = `${COLORS[level]}${level.toUpperCase()}${level === 'fatal' ? FULL_RESET : RESET}`; + const stream = LEVELS[level] >= LEVELS.warn ? process.stderr : process.stdout; + if (typeof dataOrMsg === 'string') { + stream.write(`[${ts()}] ${tag} (${process.pid}): ${MSG_COLOR}${dataOrMsg}${RESET}\n`); + } else { + stream.write( + `[${ts()}] ${tag} (${process.pid}): ${MSG_COLOR}${msg}${RESET}${formatData(dataOrMsg)}\n`, + ); + } +} + +export const logger = { + debug: (dataOrMsg: Record | string, msg?: string) => + log('debug', dataOrMsg, msg), + info: (dataOrMsg: Record | string, msg?: string) => + log('info', dataOrMsg, msg), + warn: (dataOrMsg: Record | string, msg?: string) => + log('warn', dataOrMsg, msg), + error: (dataOrMsg: Record | string, msg?: string) => + log('error', dataOrMsg, msg), + fatal: (dataOrMsg: Record | string, msg?: string) => + log('fatal', dataOrMsg, msg), +}; + +// Route uncaught errors through logger so they get timestamps in stderr process.on('uncaughtException', (err) => { logger.fatal({ err }, 'Uncaught exception'); process.exit(1); diff --git a/src/mount-security.ts b/src/mount-security.ts index 3dceea5..a724876 100644 --- a/src/mount-security.ts +++ b/src/mount-security.ts @@ -9,16 +9,10 @@ import fs from 'fs'; import os from 'os'; import path from 'path'; -import pino from 'pino'; - import { MOUNT_ALLOWLIST_PATH } from './config.js'; +import { logger } from './logger.js'; import { AdditionalMount, AllowedRoot, MountAllowlist } from './types.js'; -const logger = pino({ - level: process.env.LOG_LEVEL || 'info', - transport: { target: 'pino-pretty', options: { colorize: true } }, -}); - // Cache the allowlist in memory - only reloads on process restart let cachedAllowlist: MountAllowlist | null = null; let allowlistLoadError: string | null = null; From 7e7492ebba9296d2d669a8982aab9e3432de3752 Mon Sep 17 00:00:00 2001 From: gavrielc Date: Fri, 27 Mar 2026 15:13:39 +0300 Subject: [PATCH 5/8] style: apply prettier formatting to logger Co-Authored-By: Claude Opus 4.6 (1M context) --- src/logger.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/logger.ts b/src/logger.ts index 80cba30..6b18a9b 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -40,12 +40,18 @@ function ts(): string { return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}.${String(d.getMilliseconds()).padStart(3, '0')}`; } -function log(level: Level, dataOrMsg: Record | string, msg?: string): void { +function log( + level: Level, + dataOrMsg: Record | string, + msg?: string, +): void { if (LEVELS[level] < threshold) return; const tag = `${COLORS[level]}${level.toUpperCase()}${level === 'fatal' ? FULL_RESET : RESET}`; const stream = LEVELS[level] >= LEVELS.warn ? process.stderr : process.stdout; if (typeof dataOrMsg === 'string') { - stream.write(`[${ts()}] ${tag} (${process.pid}): ${MSG_COLOR}${dataOrMsg}${RESET}\n`); + stream.write( + `[${ts()}] ${tag} (${process.pid}): ${MSG_COLOR}${dataOrMsg}${RESET}\n`, + ); } else { stream.write( `[${ts()}] ${tag} (${process.pid}): ${MSG_COLOR}${msg}${RESET}${formatData(dataOrMsg)}\n`, From 62fc8c770811066dae83784a286810a076cdb42d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 27 Mar 2026 12:13:53 +0000 Subject: [PATCH 6/8] chore: bump version to 1.2.36 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7888048..b1dd2ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "nanoclaw", - "version": "1.2.35", + "version": "1.2.36", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "nanoclaw", - "version": "1.2.35", + "version": "1.2.36", "dependencies": { "@onecli-sh/sdk": "^0.2.0", "better-sqlite3": "11.10.0", diff --git a/package.json b/package.json index a86e33a..081d2b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nanoclaw", - "version": "1.2.35", + "version": "1.2.36", "description": "Personal Claude assistant. Lightweight, secure, customizable.", "type": "module", "main": "dist/index.js", From f900670aaf91ff6cb219a6f6499475c12d3d5e81 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 27 Mar 2026 12:13:56 +0000 Subject: [PATCH 7/8] =?UTF-8?q?docs:=20update=20token=20count=20to=2042.0k?= =?UTF-8?q?=20tokens=20=C2=B7=2021%=20of=20context=20window?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- repo-tokens/badge.svg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/repo-tokens/badge.svg b/repo-tokens/badge.svg index 58e9bb3..6e1646a 100644 --- a/repo-tokens/badge.svg +++ b/repo-tokens/badge.svg @@ -1,5 +1,5 @@ - - 41.3k tokens, 21% of context window + + 42.0k tokens, 21% of context window @@ -15,8 +15,8 @@ tokens - - 41.3k + + 42.0k From 8935e4f636ced7c94b0a56a28affb4da3581e20f Mon Sep 17 00:00:00 2001 From: James Schindler Date: Fri, 27 Mar 2026 08:28:58 -0400 Subject: [PATCH 8/8] docs: add k8s image GC known issue to debug checklist Kubernetes image garbage collection silently deletes the nanoclaw-agent image when disk usage is high because ephemeral containers don't protect the image from GC. Documents symptoms, cause, fix, and diagnosis. --- docs/DEBUG_CHECKLIST.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/DEBUG_CHECKLIST.md b/docs/DEBUG_CHECKLIST.md index 5597067..a04d88f 100644 --- a/docs/DEBUG_CHECKLIST.md +++ b/docs/DEBUG_CHECKLIST.md @@ -11,6 +11,34 @@ Both timers fire at the same time, so containers always exit via hard SIGKILL (c ### 3. Cursor advanced before agent succeeds `processGroupMessages` advances `lastAgentTimestamp` before the agent runs. If the container times out, retries find no messages (cursor already past them). Messages are permanently lost on timeout. +### 4. Kubernetes image garbage collection deletes nanoclaw-agent image + +**Symptoms**: `Container exited with code 125: pull access denied for nanoclaw-agent` — the container image disappears overnight or after a few hours, even though you just built it. + +**Cause**: If your container runtime has Kubernetes enabled (Rancher Desktop enables it by default), the kubelet runs image garbage collection when disk usage exceeds 85%. NanoClaw containers are ephemeral (run and exit), so `nanoclaw-agent:latest` is never protected by a running container. The kubelet sees it as unused and deletes it — often overnight when no messages are being processed. Other images (docker-compose services) survive because they have long-running containers referencing them. + +**Fix**: Disable Kubernetes if you don't need it: +```bash +# Rancher Desktop +rdctl set --kubernetes-enabled=false + +# Then rebuild the container image +./container/build.sh +``` + +**Diagnosis**: Check the k3s log for image GC activity: +```bash +grep -i "nanoclaw" ~/Library/Logs/rancher-desktop/k3s.log +# Look for: "Removing image to free bytes" with the nanoclaw-agent image ID +``` + +Check NanoClaw logs for image status: +```bash +grep -E "image found|image NOT found|image missing" logs/nanoclaw.log +``` + +If you need Kubernetes enabled, set `CONTAINER_IMAGE` to an image stored in a registry that the kubelet won't GC, or raise the GC thresholds. + ## Quick Status Check ```bash