Skip to content

nanocoder v1.32.0

Latest

Choose a tag to compare

@github-actions github-actions released this 04 Nov 12:43

What's Changed

  • Nanocoder's ACP agent now honours the mcpServers an editor sends on session/new, session/load and session/resume. Stdio and HTTP servers are connected for that session only, so their tools exist in that session (and in subagents it starts) and in no other, and your own MCP configuration is never read or changed. Loading or resuming a session replaces its previous servers, and closing it, or deleting it, disconnects them. A server that fails to connect, or uses SSE, is logged and skipped and the request still succeeds. initialize now reports mcpCapabilities (http). A tool whose name an existing tool already has is skipped, and these tools follow the usual approval rules. Sessions with no servers behave as before.

  • ACP extension requests now use the _ prefix ACP reserves for custom methods: _nanocoder/renameSession, _nanocoder/timeline/list, _nanocoder/timeline/revert and _nanocoder/retryTurn. The bare names still work as deprecated aliases and log a one-time notice, and will be removed in a later release. initialize now lists the exact method and notification names under agentCapabilities._meta.nanocoder, so a client can detect support instead of guessing from the version. A custom request the agent doesn't recognise now fails with the standard "Method not found" (-32601) instead of a plain error. The VS Code extension uses the advertised names and falls back to the bare ones for an older agent.

  • ACP sessions now keep their own provider and model. Changing them with session/set_config_option affects only that session (a running turn finishes on the model it started with), its subagents use the model it is on, the config options returned for a session are its own, and saved sessions record their own provider and model. session/resume and session/load restore the saved provider and model when they are still available, and otherwise stay on the default and report the saved choice in _meta["nanocoder/modelFallback"]. The agent advertises agentCapabilities._meta.nanocoder.perSessionModel.

  • ACP clients can now exempt a session from retention with the new _nanocoder/pinSession extension method (sessionId, pinned). Neither retentionDays nor maxSessions deletes a pinned session, and pinned sessions do not count toward maxSessions; session/delete still removes them. session/list entries report the mark as _meta["nanocoder/pinned"], and initialize reports the effective retentionDays and maxSessions as _meta["nanocoder/retention"].

  • ACP clients can now turn tools off for a single session. Send _meta: {"nanocoder/disabledTools": ["browser"]} on session/new, session/load or session/resume, for example to stop Nanocoder's built-in browser when the editor provides its own. The names are added to the user's own disabledTools, never replacing it, and apply to that session only: the tools are left out of the tool list and the prompt, a call the model makes to one anyway is refused and nothing runs, and subagents the session starts respect the same list. The response _meta reports the list in effect under the same key so a client can verify it. The list is not saved with the session and the user's config files are never read or changed for this. The approval policy the response reports no longer lists a tool the session has turned off. Sessions that send nothing are unchanged.

  • ACP clients can now follow each subagent run on its own. Nanocoder sends a _nanocoder/subagent notification as every run starts, progresses and ends (completed, failed or cancelled), with a stable childRunId, the parentRunId (empty for a child of the turn), the parent agent tool call, the subagent's name, progress counts and its result or error. A new _nanocoder/cancelSubagent request cancels one run by childRunId without ending the turn: the run reports cancelled, the parent agent call gets a "Subagent was cancelled" result and carries on, and cancelling the whole turn still cancels every run. The permission request for a subagent's tool call now names its run under _meta as nanocoder/childRunId. Both are listed in agentCapabilities._meta.nanocoder. Clients that ignore the new notification see the same behaviour as before.

  • Add a read-only _nanocoder/timeline/read ACP method that returns a file's before-image for a timeline checkpoint (text, created or unavailable), and report createdFiles and a per-prompt turnId in _nanocoder/timeline/list entries. truncateToMessageIndex changes with every model round-trip, so turnId is what groups the checkpoints of one prompt; checkpoints recorded before this change have no turnId.

  • Clients can now check and grant directory trust without --trust-directory. nanocoder trust --status [--json] reports whether the folder is trusted (trusted, not-trusted, changed or needs-re-approval), what it will run and its fingerprint, and loads nothing from the folder, including its .env and any preferences file in it. nanocoder trust --grant <fingerprint> saves the folder only if its fingerprint still matches, so a client can save exactly what it showed the user. nanocoder --acp now exits with code 3 when it refuses an untrusted folder, instead of 1, which other startup failures still use. Each trust entry also records a fingerprint version, so an entry saved by another version reads as needing re-approval instead of changed. A project .env can no longer set XDG_CONFIG_HOME or APPDATA, which decide where the trust record is stored. Failures to read or save preferences are now written to the log instead of being shown in the chat.

  • Added a first-class provider template for FutureInfra, an OpenAI-compatible AI API router, to the /settings providers wizard. Selecting it fills in the base URL (https://futureinfra.ai/v1/ai) so only an API key and a model name (for example openai/gpt-4o-mini) are needed.

  • Added an agent evaluation harness under benchmarks/agent/, run with pnpm run test:agent-eval. It measures pass/fail, steps, tokens and cost per task by shelling out to nanocoder --plain --json run, so a released tag can be measured with the same harness as the working tree. Six vendored task fixtures each carry machine-checkable assertions and distractor files, so a run working from degraded context fails rather than merely finishing cheaper; every result records the fixture-set id and a content hash of the fixtures. It is deliberately outside pnpm run test:all, since it needs real model calls. Phase 0 of #1197.

  • Added the Agentic CI/CD Gate: Nanocoder can now review pull requests and investigate (and optionally fix) CI failures without an interactive session.

  • git_pr gains diff, comment, review, checks and logs actions (CI run logs support search and pagination).

  • nanocoder verify --pr <n> [--post-review] runs a headless, read-only review with a semgrep pass over the PR's changed files, printing it or posting it as a PR comment. It refuses an untrusted directory (--trust-directory bypasses this for one run), and its reviewer can never comment, review or open PRs itself, whatever alwaysAllow says.

  • The per-project daemon can watch GitHub Actions for failures (ciWatch in your global preferences, requires the gh CLI), run a read-only verify-ci-investigator subagent, post the diagnosis to the branch's PR, and send OS notifications. Skills can react to failures too with a new ci.job.failed trigger (optional branches filter).

  • nanocoder daemon start --trust <comment-only|auto-fix|full-commit> (or nanocoder.verify.trustLevel in agents.config.json) lets the investigator fix the failure in an isolated clone: auto-fix pushes a new branch and opens a draft PR; full-commit pushes straight to the failing branch after a one-time per-project confirmation. A failure on a commit the daemon itself pushed as a fix is never fixed a second time, and full-commit confirmation and ciWatch are only read from your global preferences, never from a project file.

Safety notes:

  • At auto-fix and full-commit, the fix agent can edit and commit but has no shell tool, so it cannot run commands steered by a CI log; CI on its draft PR or push verifies the fix. The fix run also starts without GitHub tokens, gh login, ssh-agent or git credential helpers, reads the failed-step log the daemon fetched for it, and cannot push; the daemon publishes the result from the clone to the project's own origin.
  • verify and CI investigations always use the built-in verify-pr-review and verify-ci-investigator agents, so a pull request cannot supply its own reviewer.
  • Anything posted to GitHub passes through a secret-redaction step first, and semgrep receives the changed paths after --.
  • A verify.trustLevel of auto-fix or full-commit in agents.config.json needs confirmation at daemon start; unattended starts, including the installed service, fall back to comment-only. --trust=<level> is accepted, daemon status shows the running level, and starting with a different level while a daemon runs says it was not applied.
  • CI watch keeps polling after unexpected errors (minimum interval 5 seconds), and its state file is git-ignored.
  • Add an API Route template to the provider setup wizard.
  • Add nanocoder.autoCommit (#1482). When set to true in agents.config.json, every successful agent file edit (write_file, string_replace, diff_edit) is committed on its own, with a Conventional Commit message the current model writes from that file's diff (the /commit prompt), falling back to chore: update <path> if the model call fails. Only the edited file is committed, so the user's own staged and unstaged work is never swept in. Auto-commit is skipped outside a git repository, for ignored files, for no-op edits, and while a merge, rebase, cherry-pick or revert is in progress; a failed commit is logged as a warning and never fails the tool call. Off by default.

Daemon-triggered runs also use their model to write the message.

  • A failing test or type-check run that is too long to pass to the model whole now reaches it as a failure capsule instead of a head-and-tail cut, which used to drop the failure detail printed in the middle of the log. Nanocoder recognises Jest, Vitest, Mocha, AVA and tsc output, keeps each failing test's title, location, assertion and expected/received diff, and narrows the original command to the first failing test (for example npm test -- jest/auth.test.js -t '...'). It re-runs that narrow command once to report whether the failure reproduces on its own. Only plain runner invocations are narrowed or re-run; !command input is distilled but never re-run; short or unrecognised output is unchanged. Closes #1584.

The re-run never repeats pre/post package scripts: npm re-runs pass --ignore-scripts when the script has such hooks, and pnpm, yarn and bun scripts with hooks are not re-run automatically.

  • Add a built-in browser tool (navigate, click, type, screenshot) on one shared headless Chromium page. Screenshots reach vision models as images. On OpenAI-compatible providers they follow the tool result as an image message. Models that models.dev lists as text-only get the caption without the image. Localhost is allowed. Private network and cloud metadata addresses are blocked. Chromium itself is installed with npx playwright-core install chromium (add --with-deps on Linux).

Hostnames are resolved before loading, as fetch_url does, so a public name that points at a private or metadata address (for example 169.254.169.254.nip.io) is blocked on navigation, on every page request and after redirects. When Chromium is missing, the error suggests npx playwright-core@<bundled version> install chromium. Models that models.dev does not list still get screenshots, but if the provider rejects a request for carrying images, it is retried once without them and later requests in the session leave them out, so a text-only local model cannot get stuck on a screenshot in history.

  • Enhanced /help with categorized command listings and command-specific details, including usage, supported options and subcommands, aliases, and examples. Closes #1308.

Command details match what each command accepts, including the real /settings tabs, and /usage and /compact list their ?key=value overrides.

  • Added bounded recovery evidence for failed string_replace and diff_edit searches, including likely target ranges, exact current text, and whitespace or code-difference notes. Ambiguous targets and oversized evidence prompt a focused read before retrying. Failed edits remain exact-match-only and leave files unchanged.
  • Add nanocoder.formatters: code formatters (Prettier, Biome, gofmt, ...) that run on every file the agent writes whose path matches a glob, before post-tool-use hooks fire. The model is told when a formatter changed the file so its next edit starts from what is on disk. Closes #1483.
  • Project memories now record the git commit, branch and a content hash of every tracked file they mention when they are saved. On recall, a memory whose referenced file has since been edited (including uncommitted edits) or deleted is injected with a short [WARNING: recorded at <commit>, <file> has changed since. Verify before trusting.] prefix, so the model checks the file instead of trusting stale context. Memories saved before this change, and memories saved outside a git repository, are recalled exactly as before. Closes #1572.
  • The MCP setup wizard (/settings mcp) now offers an FXMacroData template on the remote servers tab. It builds a remote HTTP MCP config pointing at https://mcp.fxmacrodata.com, giving the agent macroeconomic releases, central bank rates, release calendars and FX data across 22 currencies. The API key is optional: leave it empty to use the keyless tier (the last 90 days of USD releases, each readable 15 minutes after publication, the USD release calendar, press releases and data catalogue, plus market sessions for every currency), or enter a key and it is sent as a bearer Authorization header. Keys containing spaces, line breaks or control characters are rejected in the form.
  • Added ?key=value inline overrides to slash commands. An override is applied via the existing session-override plumbing and restored to its prior value when the command finishes, so users can test a setting for a single command without changing the session state. Closes #1151.

Working examples:

  • /usage ?context-max=200k - renders context usage against a 200k limit for this command only, then restores the prior limit.
  • /compact ?threshold=80 - gates the manual compaction on current usage: compacts normally at or above 80%, otherwise reports the usage and skips (mirroring the automatic path's gate). Composes with ?context-max, which the gate reads as the limit.
  • /compact ?preview - boolean flags forwarded as their --flag long forms.

Behavior:

  • threshold values outside 50–95 and unparseable values are ignored (fail open: the command runs un-gated).
  • auto-compact is parsed and round-tripped through the session-override stores (apply then restore), but no built-in slash command currently reads it synchronously during dispatch — reserved plumbing for the automatic compaction path.
  • Unknown ?foo=bar keys are never consumed: the dispatcher forwards them to the command handler verbatim (so each command's own unknown-arg handling runs) and queues one warning naming the key, so a typo cannot silently no-op.
  • Values containing additional = are preserved (?config=key=value → 'key=value').
  • Override applies only to the current command; it does not affect global session state.
  • Custom commands and MCP prompts bypass override parsing (they receive ?foo=1 literally).

Overrides are parsed only for /compact and /usage, so other commands receive ?word text unchanged, and every override is restored when the command finishes.

  • Retry unhealthy MCP servers on periodic health checks with exponential backoff and restore tools automatically when servers recover

A server whose connection closed (a crashed stdio server or dropped socket) is reconnected from its config with the same backoff, and its tools come back once it does.

A remote (HTTP or WebSocket) server that restarts and no longer knows its session is reconnected the same way, instead of being pinged with the stale session forever.

  • Added MCP server liveness checks that detect failed pings and transport closures, remove unavailable tools, and show unhealthy servers in /mcp.

Pings are skipped while a tool call or resource read is in flight, a server is marked unhealthy only after two missed pings in a row (a closed transport is reported at once), and healthCheckInterval: 0 disables pings entirely.

  • Add in-process plugins under .nanocoder/plugins that can block a tool, annotate its result, observe compaction, deny a permission prompt, or append prompt context.

Plugins also load in daemon-triggered runs after the daemon's trust check.

  • Start an interactive Nanocoder session on a GitHub pull request in a dedicated sibling worktree with nanocoder worktree <PR-number>, preserving the launching checkout. Closes #1486.
  • Global options such as --provider and --mode can follow the PR number.
  • read_file now returns a short stub when the same path and line range is read again and the file has not changed. Compact and /clear force a real read. Refs #793.

The stub is also invalidated when the empty-response compact-and-retry runs, and when the message cap drops the earlier read.

  • Added shell-style reverse prompt history search (Ctrl+R / bck-i-search) in the CLI chat input. Users can now search past prompts chronologically with case-insensitive substring matching, cycle through older matches by pressing Ctrl+R repeatedly, accept and populate the input field with Enter (preserving any attachments or placeholders), or cancel and restore their in-progress draft with Esc. The reasoning trace toggle shortcut has moved from Ctrl+R to Ctrl+N (Ctrl+G is voice push-to-talk and Ctrl+T stays reserved for the live task list). Closes #936.

Malformed prompt history entries are cleaned up on load so they cannot break the search.

  • Add nanocoder storage, a read-only interactive inspector for saved sessions and artifacts across Nanocoder and timeline/checkpoint data in the current project. Use nanocoder storage --format json for a non-interactive report suitable for scripts.
  • The directory trust prompt now lists the plugins, hooks, formatters, LSP servers, MCP servers, custom tools, and subscribed skills, agents, and commands in the folder. Trust stores a fingerprint of those files, and the prompt comes back when they change. A folder trusted before this change is asked again once. Older versions can't read the new trustedDirectories entries and fail the trust check, so remove them before downgrading. Closes #1659.

The fingerprint also covers the project .env, the agents.config.json settings that relax confirmations (alwaysAllow, defaultMode, sandbox, verify.trustLevel) and project provider baseUrls.

  • Added voice mode. /voice turns it on, with push-to-talk on Ctrl+G or hands-free speech detection, local Whisper speech-to-text and local Piper text-to-speech by default, and opt-in OpenAI cloud STT/TTS. Speaking or pressing Ctrl+G interrupts a running response. The audio tools are installed on first use, with checksums verified. Hands-free mode is paused in yolo mode so background speech cannot trigger unconfirmed tool calls. Thanks to @RONAK-AI647. Closes #622.

  • Cancelling a bash command now also stops background processes it left running, and force-kills any that ignore the normal stop signal after two seconds.

  • The Whisper model is downloaded with a checksum on every platform, a missing model counts as an uninstalled dependency, and installer output is captured instead of drawn over the UI. Ctrl+G is listed in the ? shortcuts overlay.

  • Voice state shows on the mode line under the prompt (voice (Ctrl+G to talk), ● listening, transcribing…, speaking) instead of in a separate bordered panel, and voice notices print as plain lines.

  • On macOS the installer only asks Homebrew for the tools that are missing (sox, whisper.cpp), prefers native Homebrew in /opt/homebrew on Apple silicon, and explains a failed Homebrew install in a few lines (for example Intel Homebrew running under Rosetta, or an outdated Xcode) with the command to rerun for the full output.

  • The VS Code chat panel now queues follow-up prompts submitted while a turn is still running, instead of firing a second overlapping ACP request (which the agent rejected with "Prompt already in progress") or leaving the thought sitting un-submitted in the composer.
    Queued messages appear inline as user bubbles with a Queued badge and a remove button; when the current turn finishes they dequeue in order.
    While a turn is running, typing in the composer flips Stop back to Send so clicking it queues the follow-up (Escape or an empty-composer Stop still cancels). The webview parks that follow-up locally and only forwards it after the in-flight ACP prompt() settles — posting it immediately was what produced the "RequestError: Internal error" toast.
    Stop / Escape (and New Chat) discard the whole queue so cancelling a runaway agent does not immediately start the next waiting prompt.

  • The VS Code chat panel now syntax-highlights code blocks as they stream in, and shows a blinking cursor at the end of the text the agent is still writing. Code blocks used to render with no colour at all, while streaming and once finished. They are now highlighted as soon as the fence opens, without waiting for the closing backticks, and a language name that is still being typed (py, pyth, python) falls back to plain text rather than erroring. The colours follow the light or dark VS Code theme. Highlighting uses a small vendored highlight.js bundle (16 common languages, the same static-file approach as marked.min.js, no new dependency), and the cursor is pure CSS that stops when the turn ends, is cancelled, or a tool call starts. Closes #751.

  • ACP clients can now see which tools skip the permission prompt. The session/new, session/load and session/resume responses list, under _meta["nanocoder/approval"].alwaysAllow, the tools offered in the session that an alwaysAllow list (the top-level one or an MCP server's) lets run without asking, so a client can tell that normal mode will not ask for them. How approvals are decided is unchanged. Closes #1910.

  • ACP clients can now close a session. Nanocoder had advertised session/close since ACP sessions were added but never handled it, so a client that called it got a method-not-found error. Closing cancels any running turn, stops prompts already queued behind it from starting, releases the session's timeline lock and drops the session from memory. The session stays on disk, so it can be loaded or resumed later.

Closing waits for the cancelled turn to finish before releasing the timeline lock, and clears the session's cached file-read state.

  • ACP custom commands fill {{args}} and named parameters from the text after the command name, the same way the CLI does. The model no longer sees the placeholder or the raw slash line above the body. Attached files go after the body and never fill an argument. Closes #1639.
  • nanocoder --acp now refuses a directory you haven't trusted, the same check nanocoder run makes, before it loads that directory's project config (providers, hooks, MCP servers). Trust the directory by running nanocoder interactively there once, set NANOCODER_TRUST_DIRECTORY=1, or pass --trust-directory for one run. The VS Code extension passes --trust-directory only when the workspace is trusted in VS Code's Workspace Trust, so it keeps working in folders you've trusted there.
  • ACP editors now see a failed tool call as failed. The status was set to failed only when the result text started with "Error", so a command that exited non-zero, a missing file and a validation failure all reached the editor as completed. The status now also follows the isError flag that processToolUse already sets for these, which also stops a write_plan that failed validation from counting as a written plan.
  • Fix the /help text of the VS Code chat showing a garbled character before each command description. source/acp/acp-agent.ts contained invalid UTF-8 bytes where an em dash was meant; they are now real em dashes, which also lets Biome read and format the file again.
  • ACP session/load now fails with Session not found on disk: <id> for a session ID that is neither in memory nor on disk, the same error session/resume already returned. It used to register an empty session and succeed, so a client could not tell that a stored session was gone, and a prompt sent to that ID saved a new session under an ID the client had invented. Loading a session that exists, in memory or on disk, is unchanged.
  • Switching mode in an ACP editor while a prompt is running now takes effect straight away. Previously the running prompt kept the mode it started in, so moving from yolo or auto-accept back to normal still let the next commands and file edits run without a permission prompt. The next model request is offered the tools of the new mode, each call is approved under it, and a call the model had already returned that the new mode no longer offers (for example an edit after switching to plan) is refused instead of run. A call that is already running is not interrupted. Closes #1857.
  • ACP no longer falls back to auto-accept when a mode is wrong. session/set_mode now rejects an unknown mode ID with an invalid-params error and leaves the mode unchanged, where a typo or a renamed mode used to turn approvals off silently. nanocoder --acp --mode <mode> now starts every new, loaded and resumed session in that mode instead of ignoring the flag. A session/set_mode that succeeds is confirmed with a current_mode_update notification, so a client can check the mode that was applied. Closes #1900.
  • Built-in slash command replies over ACP (/help, /copy, /model, /provider, /settings, /init, /theme, /compact, /context-max, /usage) no longer describe the VS Code sidebar to other clients. The VS Code extension, recognised by the clientInfo it sends in initialize, keeps its wording unchanged; every other client, including one that sends no clientInfo, gets neutral text.
  • Fixed ACP sessions ignoring the working directory the editor sends in session/new. File tools, execute_bash, the git tools, the project boundary for file paths, the system prompt and AGENTS.md used the directory the agent process was spawned in, so when an editor started the agent elsewhere (for example with the VS Code nanocoder.cwd setting, or when resuming a session from another project) relative paths read and wrote the wrong files and absolute paths inside the real project were rejected. Each ACP session now works in its own cwd, a cd in one session no longer moves another, and the interactive CLI is unchanged. Project-level configuration is still read from the directory the agent starts in. Thanks to @diyaavirmani. Closes #1707.
  • ACP editors now see everything a subagent does. Every subagent tool call is announced to the editor as it starts, not only the ones that need approval, and each one settles with the tool's real result: a call that fails after it was approved, or that reports its failure as an Error: ... message, now shows as failed instead of completed, and a call the subagent is refused (an unknown tool, or one its mode does not allow) leaves a failed card instead of nothing. A subagent follows its session's mode: a yolo or auto-accept session is no longer prompted inside a subagent for tools it would not be prompted for at the top level, and a plan session's subagents are no longer offered the editing tools. The non-interactive plain shell likewise gives its subagents the tools of its --mode. Each session's prompts reach its own editor, so two sessions running subagents at once can no longer answer each other's approvals. Closes #1105.
  • Fix the architect review bar's accent border sitting out of line with the composer and every other footer modal. ArchitectReviewPrompt was missing the shared left-edge wrapper PlanReviewPrompt, FileExplorer, IdeSelector and ModalSelectors already use. Closes #1532.
  • Cap the Architect review bar's changed/new-file lists at 5 rows with a "+N more" line, matching the same cap already used for the live tool-count summary. It used to print every file with no limit, so on a turn touching many files the list pushed the Keep / Revert / Revert & Revise choices below the bottom of the screen. Closes #1533.
  • Fixed every start deleting the plan, task list and walkthrough of saved sessions older than a day when sessions.autoSave is false. The startup artifact sweep ran before the session store was set up, so it found no saved sessions and treated every artifact folder as orphaned. It now looks up the sessions directory itself (read-only, without creating the store or running retention), counts both the session files on disk and the index as saved sessions, and keeps everything if the store exists but cannot be read. Folders belonging to sessions that were never saved are still cleaned up as before. Thanks to @diyaavirmani. Closes #1704.
  • Restore exact prior git staging state on auto-commit failure and clarify same-file edit behavior in documentation
  • Fix nanocoder.autoCommit committing other files when the edited file's name looks like a git glob. The path was passed to git add, git diff --cached and git commit as a pathspec, so an agent edit to [ab].txt also matched a.txt and b.txt and swept the user's uncommitted edits to them into the agent's commit. Auto-commit now runs git with --literal-pathspecs, so only the edited file is committed. Closes #1654.
  • Fixed Backspace acting as forward Delete in the prompt (and in the MCP wizard) on npm installs, and restored the Shift+Enter / Alt+Enter newline chords that depend on the same check. The key-disambiguation relied on a pnpm patch to Ink that only applied inside the repo, so published installs ran stock Ink and lost it. The raw key sequence is now read from Ink's stdin event emitter, which needs no patch.
  • execute_bash no longer hangs on a command that starts a background job. A command such as npm run dev & or sleep 100 & kept the tool waiting until the background job exited, because the job held the command's output pipes open. The tool now returns once the shell itself exits and its output has drained, and the background job keeps running (it is still stopped when Nanocoder exits).
  • A shell command that exits non-zero is no longer displayed as a success in the interactive TUI. execute_bash only prefixed its result with Error: when the command could not be spawned at all, and the display layer decided success purely by sniffing for that prefix, so a broken build or a failing test run rendered as ⚒ Ran 1 command, identical to a clean run. The streamed bash path now sets ToolResult.isError on a non-zero exit and the compact display branches on that flag, showing ⚒ execute_bash failed instead. Together with the earlier --plain/--json/ACP fix, this closes #1371.
  • Fixed commands started by execute_bash running on after nanocoder had exited and writing to the project. Each command runs in its own process group, so it did not get the hangup when the terminal closed, and a command that ignored SIGTERM outlived a timeout or cancel whenever nanocoder exited within the 2 seconds before its SIGKILL fallback. Closing the terminal or dropping an SSH session (SIGHUP) now shuts nanocoder down the same way SIGTERM does, and shutdown stops every command that is still running, including jobs a finished command left in the background with cmd &: SIGTERM first, then SIGKILL after 2 seconds. A timeout or cancel is now reported only once the command has actually stopped, so a nanocoder run can no longer finish around it. With the Linux sandbox on (nanocoder.sandbox), a cancel, timeout or shutdown now reaches the command at all: bwrap starts it in a session of its own, and only bwrap itself was being stopped. Thanks to @diyaavirmani. Closes #1833.
  • execute_bash on Windows no longer mangles double-quoted arguments. The command was handed to cmd /c as an ordinary argv entry, so Node re-quoted it and escaped every inner " as \", which cmd.exe does not understand. echo "hello world" printed the backslashes, git commit -m "fix the bug" failed with pathspec 'the' did not match, and node -e "console.log(1+1)" exited 0 with no output, so the agent believed it had worked. It now spawns cmd.exe the way custom tools already do (/d /v:off /s /c "<command>" with verbatim arguments), and a Windows-only spec covers echo, node -e, git commit -m and git log --format, which now runs in the Windows CI job. Fixes #1663.
  • Fix /copy argument validation so unknown arguments show a usage warning instead of copying the full assistant response.
  • A git branch name containing CJK characters or emoji no longer breaks the welcome screen's location line. The row was budgeted by counting characters, but a wide glyph is one character and two terminal columns, so the line overflowed and split into a branch row with a dangling separator and a stranded path. Path truncation now measures display width, which also stops an emoji being cut in half. Closes #1389.
  • Keep failed update checks out of the chat, throttle successful automatic checks to once every 24 hours, and support opting out with NANOCODER_NO_UPDATE_CHECK=1. The /update command continues to perform a fresh check. Closes #1738.

Within the 24 hours the banner keeps showing, without a network request, while a newer version found by the last check is available.

  • Nanocoder now rejects unknown top-level flags and commands before starting the app, suggesting the intended spelling when possible. This prevents a typo such as --mdoe from silently leaving run in its default auto-accept mode. Closes #1736.

An option typed before a subcommand, as in nanocoder --trust-directory daemon start, gets a hint to put the command first instead of an unknown-command error.

  • Show when automatic compaction falls back to a mechanical summary after the LLM summary fails. Closes #1518.
  • A ChatGPT Codex token refresh keeps the saved account id, and keeps the refresh token when the token endpoint does not send a new one. The session uses the new access token after that, so the next request does not refresh again with the old one. Closes #1632.
  • Fix daemon shutdown to wait for active tasks before disconnecting MCP servers and removing the lockfile.
  • Fix the compact tool-activity summary showing identical "Ran N git command(s)" rows for git_status, git_diff and git_log, making a turn that mixes them indistinguishable. Each git tool now gets its own phrasing. Closes #1556.
  • Quote custom tool template values for cmd.exe so spaces, command metacharacters, and embedded quotes stay literal on Windows. Values containing percent signs, carriage returns, newlines, or null bytes are rejected because cmd.exe cannot represent them safely in a command string. This is a deliberate cross-platform restriction: values accepted by POSIX shells may be rejected by cmd.exe. Closes #1084.
  • nanocoder daemon install now warns when the project isn't trusted yet. The installed service runs nanocoder daemon start unattended on a restart-on-failure policy, and daemon start refuses to boot outside a trusted directory - so installing auto-start before ever running nanocoder there produced a service that failed forever with no explanation, while install itself reported plain success. The result message now names both fixes: run nanocoder there once to accept the trust disclaimer, or pass --trust-directory.

The suggested fix is to run nanocoder there once, or NANOCODER_TRUST_DIRECTORY=1 nanocoder daemon start once, since --trust-directory only covers a single run.

  • Fix nanocoder daemon start --trust-directory failing with Unknown flag: "--trust-directory". cli.tsx already forwarded the flag to the daemon and the help text lists it, but the start parser rejected it. The parser now accepts it, so the flag skips the directory trust check for that start only. It can be combined with --trust <level>, which is a separate setting: the CI investigation trust level, unrelated to directory trust.
  • Two fixes around the daemon start trust gate, both about what users are told rather than about the gate itself, which already refuses correctly.

ensureDirectoryTrust reported persisted: true whenever it attempted the NANOCODER_TRUST_DIRECTORY=1 write, but savePreferences swallows write errors and returned void either way. On a failed write (read-only config dir, full disk) daemon start printed "Marked

as trusted" while the next boot's own gate refused the directory — the message was untrue and the user had no way to tell. savePreferences now returns whether the write landed, and the trust result only claims a persisted entry when it did. Its return type is intentionally part of the public DirectoryTrustDeps contract and accepts boolean | void, so existing injected savers that return nothing keep working.

A failed write is not silent either: daemon start prints a warning naming the directory and saying the next start will refuse it again. The trust result carries a distinct persistFailed flag for this, because persisted: false alone also covers --trust-directory and an already-recorded folder — paths where nothing went wrong and a warning would be wrong.

docs/features/skills.md and docs/features/scheduler.md document the gate where users meet it: the trust options, and the daemon install asymmetry — install writes the auto-start unit and warns when the project is untrusted, while the gate itself fires on every unattended boot. The refusal comes from daemon start before the daemon child that owns daemon.log is spawned, so it goes to that command's own output, not to the log file.

  • Warn and skip auto-commits on detached HEAD, and mark agent-created commits with a searchable trailer. Closes #1747.
  • Fix diff preview edge cases in VS Code extension:
  • Resolve diff collisions when ACP notifications carry multiple diff blocks under the same tool call ID by uniquely keying pending changes with compound IDs, and open only the matching file diff when clicked from the chat webview.
  • Avoid terminal focus stealing when opening a diff preview from the chat webview.
  • Add clearPendingChanges() to clean up pending diffs and virtual documents on session reset, resume, and new chat.
  • Preserve HTML literals inside fenced markdown code blocks by extracting code blocks before HTML entity decoding and
    conversion. Adds tests to ensure HTML tags and entities remain unchanged inside code fences. Thanks to @Shreya657. Closes #1591.
  • An emoji in the prompt no longer desyncs the caret. The cursor was drawn by counting code points while every offset the edit keys used counted UTF-16 units, and an emoji is one of the former but two of the latter. With 😀abc in the composer the caret vanished on the first Left, then sat on the wrong character, and Backspace or Delete could split the emoji's surrogate pair and leave a lone surrogate in the message. The caret now renders on the right character, and Left, Right, Ctrl+B, Ctrl+F, Backspace, Delete and Up/Down all move over or remove an emoji as one character. Closes #1452.
  • Fixed pressing Enter immediately after typing a slash-command fragment submitting the raw fragment. The completion menu opens a render after the keystroke, so an Enter landing in between saw it closed. Enter now selects from the completions about to be shown, and still submits a command once its completion has been selected. Closes #1327.
  • Pressing Enter on a fully typed slash command now runs it on the first press instead of needing a second Enter to dismiss the completion menu. The menu opens with the exact, unambiguous match already highlighted, and Enter used to "select" it - re-applying text that was already there and closing the menu - rather than submitting. Partly typed commands still complete on Enter as before. Closes #1431.
  • Fixed structured error info labelling validation errors as network or timeout errors when the message happened to mention words like "connection" or "timeout". An error's name and Node error code still decide network and timeout errors on their own; message keywords now only count when the error is not a validation error. Closes #1136.
  • Pressing Escape on an empty composer no longer offers to "clear" it. Escape armed the two-press clear hint unconditionally, so it appeared even when there was no text, no attachments and no active-editor pill to clear. It now only arms when there is something to clear, and Escape still clears an attached VS Code file pill on its own when the composer text and attachments are otherwise empty. Closes #1430.
  • Pressing Escape in the composer no longer leaves it unable to accept typing. Ink drops keyboard focus on every Escape, and only the second Escape that clears the prompt took it back, so dismissing the next-command suggestion, closing the ? shortcuts legend or the command menu, arming the clear hint, or pressing Escape on an empty prompt left the input dead until a turn ended or Shift+Tab was pressed. Every Escape that leaves the composer on screen now returns focus to it.
  • Bare /expand cuts each argument by terminal columns instead of UTF-16 code units. An emoji at the cut point no longer leaves half a surrogate pair (shown as a replacement character), and a CJK path no longer runs past the box and wraps, because each ideograph now counts as the 2 columns it takes. Fixes #1702.
  • Fixed /expand list rows: multi-line command arguments are collapsed to a single line, cut to the width the terminal leaves for the row, and the id column is padded so tool names stay aligned past result 9. Thanks to @theluckystrike. Closes #1534.
  • /explorer no longer strands the selection when its list gets shorter. Leaving a search swaps the list from every match back to only the expanded rows, but the selected row was only ever clamped inside the arrow-key handlers, so after a long search it pointed past the end of the tree: no row highlighted, the path readout blank, Up and Enter doing nothing, and Down jumping straight to the last row. The selection is now pulled back into range whenever the list shrinks. Closes #1454.
  • Fail initialization when a provider selected with --provider (including nanocoder --acp --provider) cannot be created, instead of silently sending prompts to another configured provider. Switching provider over ACP now fails the same way and leaves the session on its current provider; before, the editor was told the switch succeeded while requests went to whichever other provider happened to initialize. The saved and default provider keep their existing fallback.
  • Commands that finish straight after starting, such as nanocoder verify --help or nanocoder daemon status, no longer exit with code 1 after succeeding. The log file was opened asynchronously, and exiting before it was ready made the logger's exit hook throw "sonic boom is not ready yet", which was reported as a fatal error. The log file is now opened before logging starts.
  • fetch_url now looks up the address behind a hostname and refuses it when any resolved address is loopback, private, link-local or a cloud-metadata address. Previously only the hostname text and IP literals were checked, so a public DNS name that points inward (for example localtest.me or 169.254.169.254.nip.io) could reach internal services without a prompt. The check runs on the first URL and on every redirect hop.
  • Add total byte budget (32 MB) and single-file size threshold (4 MB) to read_file cache, split lines on demand, and sweep expired TTL entries on insert. Refs #1781.
  • Fixed file_op deleting or overwriting files without a prompt in auto-accept, the mode ACP sessions start in. A delete, or a move or copy onto a file that already exists, now asks for approval there the same way rm through execute_bash does, because it shows no diff and cannot be undone. File edits, mkdir, and moves or copies to a new path still run unprompted. architect (which checkpoints the change), headless runs, yolo, and tools listed in alwaysAllow are unchanged. Non-interactive nanocoder run stops with Tool approval required on such a call, as it already does for bash. Thanks to @diyaavirmani. Closes #1683.
  • Fixed TimelineManager pruning an active session whose directory mtimeMs was older than MAX_TIMELINE_SESSION_AGE_MS. Each session now holds a per-process lockfile at .nanocoder/timeline/<sessionId>/.lock, published atomically via a temp file plus hard link so concurrent readers never observe a half-written lock; pruneStaleSessions probes the lock and removes the entry only when the lock is missing, malformed, held by a dead PID, or older than 24h by file mtime. The mtime is refreshed on every capture so long-lived sessions keep their protection, a resumed session reaps a dead predecessor's lock before acquiring, clear() resets the in-memory claim so the next capture re-acquires, and a session whose lock was reaped by another process re-acquires it on its next capture. Closes #1149.
  • Fix the prompt history file being wiped when it is truncated or has invalid content. A crash mid-save could leave .nano-coder-history starting with the JSON marker followed by broken JSON; the next load treated the parse failure exactly like a missing file, and the next prompt replaced the file with that empty history. The bad file is now moved aside to .nano-coder-history.corrupt (a timestamped name if one is already there, so a later corruption never overwrites an earlier backup) and saves write to a temporary file that is renamed into place so a crash can no longer truncate the history. A history file that merely cannot be read (for example a permissions error) is not treated as corrupt: it is left where it is and not overwritten, and the history comes back once the cause is fixed. Saving keeps the file's permissions and writes through a symlinked history file. Thanks to @Vishesh-Verma-07. Closes #1638.
  • Fixed the VS Code extension's CLI discovery so a foreign project that happens to compile a script to dist/cli.js is no longer executed as the Nanocoder CLI. The local development fallback now requires the workspace folder's package.json to name @nanocollective/nanocoder (#1725); other folders are skipped and discovery continues to the installed CLI.
  • Fixed the /schedule description of step and six-field cron expressions. */15 * * * * now reads as every 15 minutes, 0 */2 * * * as every 2 hours, and a leading seconds field no longer shifts the remaining fields (0 0 9 * * * reads as daily at 9:00). A seconds field other than 0, and a wildcard minute with a step hour, now return the raw expression instead of a wrong sentence.
  • Keep globalThis.process intact when fetch_url or file caching converts HTML through get-md: pages with an <iframe> make happy-dom windows that null the global after get-md's own restore, and the next process read crashed the app through the fatal-error handler. The logging config, logger provider and shutdown manager now use a module-level process reference, the uncaught-exception and unhandled-rejection handlers fall back to stderr when the logger itself throws, and the conversion calls restore the global afterwards. Closes #1553.
  • Fixed Homebrew installs and upgrades failing checksum verification for the 1.31.0 npm tarball by correcting the formula's SHA-256 checksum. Thanks to @niukanen1. Closes #1601.
  • Fix loadAppConfig stripping the .source field when unwrapping MCP server configs, which silently disabled validateProjectConfigSecurity's hardcoded-credential scanner for project-level MCP servers. Closes #1248.
  • Shift+Enter now adds a line break at the cursor instead of scrambling the message. Typing one, Shift+Enter, two, Shift+Enter, three used to submit onetwothree with two trailing blank lines, because the break was appended to the end of the composer value while the caret stayed put, so every later word landed at the stale offset. Ctrl+J was affected the same way and only looked right because the caret is usually already at the end. Closes #1326.
  • Improve provider connection error messages with actionable diagnostics.
  • Fix git_log ignoring its branch argument. It always returned the commits of the checked-out branch while labelling them with the requested one, so asking for the feature branch from main showed main's history under a feature header. The branch is now passed to git log, and a branch value starting with - is rejected so it can't be read as a git option. Closes #1587.
  • /help <command> now shows the real syntax for /export (including --json), /privacy inspect <text>, and the /tasks list, start, done and complete subcommands. In the VS Code extension, /model and /provider now point at the model picker and the Configuration popover in the composer instead of a "chat header" that does not exist.
  • Prevent accidental commits of sensitive checkpoint conversations and runtime state by automatically maintaining .nanocoder/.gitignore, while keeping user configuration files trackable. Closes #1739.
  • Fixed the prompt box sitting a couple of columns right of the chat transcript in inline mode (--no-alt-screen). The box and the mode line below it now share the transcript's left edge. Fullscreen is unchanged. Closes #1432.
  • Replies with many cut-off JSON tool calls no longer freeze the tool-call parser. Before, each unfinished call could rescan the rest of the reply, so a few hundred KB of broken calls took seconds to parse. The scan now stops at a raw line break inside a JSON string and has a fixed work limit per reply. A reply that uses up the limit is rejected with a retry prompt instead of running XML that may be quoted inside an unscanned call. JSON-looking text inside a valid XML call's arguments, such as a write_file of docs, is still treated as that call's content. Closes #1803.
  • Fix /copy extra-argument validation and update command help descriptions.
  • Speed up startup by checking git/gh availability without spawning child processes. Importing the tool registry no longer blocks the first frame with git --version, git rev-parse, and gh --version calls; availability is now resolved lazily from the filesystem when the tool registry is built. Behavior is unchanged: git tools are still offered inside a repository, hidden outside, and git_pr still requires the gh CLI. Thanks to @Vishesh-Verma-07. Closes #1819.
  • Lazy-load cli-highlight, highlight.js (193 language modules) and parse5 until syntax highlighting is actually needed. highlight() is now wrapped in a synchronous lazy-loader so startup commands like --version, idle TUI sessions, and --plain runs that never display a fenced code block or file preview no longer pay the startup import and language registration cost. Closes #1776.
  • Load the ws package lazily so the VS Code WebSocket server's dependencies stay out of the startup module graph unless --vscode is used. Previously every boot — interactive, run, and the daemon — loaded ws and its transitive modules even though the server only ever starts in VS Code mode; they are now fetched on first server start, trimming the startup graph. Thanks to @Vishesh-Verma-07. Closes #1821.
  • Warnings and errors now reach the log file. The shared logger used to stay on its console fallback for the whole process, so only the few app-startup lines were written to the file, and setting NANOCODER_LOG_LEVEL printed log lines to stdout instead, which broke nanocoder run --json output and ACP's JSON-RPC stream. Logs now go to the daily log file at the configured level and never to stdout; the Bun fallback logger writes to stderr. The last lines before the process exits are flushed so they are not lost.
  • Language server crashes are no longer silent. When a server exits unexpectedly, in-flight requests fail immediately instead of hanging until the 30s timeout, the user sees an error with the tail of the server's stderr, stale diagnostics from that server are dropped, and Nanocoder restarts the server once. lsp_get_diagnostics now says the server crashed (and whether it is restarting) instead of claiming the file type is unsupported. Closes #1515.
  • lsp_get_diagnostics no longer reports a broken file as clean. The first call after opening a file asked before the language server had checked it, and a relative path was turned into a malformed file:// URI that never matched the diagnostics the server published, so both returned "No diagnostics found". Relative paths now resolve against the session directory, a newly opened file waits briefly for the server's first result, and an unchanged file is not re-sent. When a language server fails to start, the diagnostics error now includes its reason (for example typescript-language-server's "Could not find a valid TypeScript installation") instead of only saying no server is available.
  • lsp_get_diagnostics now only accepts files inside the project, like read_file. It used to pass any existing path to the language server, which reads the file's contents, so a path such as ../other/file.ts or an absolute path elsewhere on disk was accepted. Such paths are now rejected with the same "Path must be within the project directory" error the file tools give.
  • Preserve literal Markdown placeholder tokens in assistant replies.
  • Nanocoder now warns when two MCP servers expose a tool with the same name. Tools are keyed by name, so only one server's tool was reachable and the other was hidden without any notice. The warning names each shared tool, the servers that expose it, and the server its calls go to.
  • Fixed Backspace doing nothing in the MCP setup wizard's custom-server environment variables field. It now removes the last character, including a line break, like every other text field. Closes #1406.
  • Editing a renamed GitHub remote MCP server keeps its bearer token (#1424).
  • The (Shift+Tab to cycle) hint in the status line now shows by default instead of only on terminals under 80 columns. It was gated on the narrow-terminal check, so it appeared where space was tightest and never on a normal-width terminal; it is now included whenever the row has room and is dropped by the existing width budget when it doesn't. Closes #1453.
  • Ctrl+A, Ctrl+E, Ctrl+U and Ctrl+K in the prompt now act on the current line, not the whole multi-line buffer. Ctrl+U on the last line of a three-line prompt used to clear all three lines with no undo, and Ctrl+A/Ctrl+E jumped to the very start/end of the prompt instead of the current line, matching the ? shortcuts legend's "Move/Delete to start or end of line" only when the prompt had a single line. Closes #1530.
  • Adds the nanocoder.ui.maxFps preference (integer 1–60, default 30) to cap Ink’s TUI rendering rate on slow terminals and resource-constrained CPUs.
  • Added a next-command suggestion after turns that edit files. When a turn makes a successful write_file or string_replace edit, the empty prompt now suggests a relevant follow-up: /commit when changes are already staged, otherwise /checkpoint create. The suggestion is only placeholder text — typing replaces it, Tab inserts the command, Esc dismisses it, and sending a message clears it — and a slash command finishing (the suggested one included) does not bring it back. Closes #1317.
  • Request streaming usage metrics by default (includeUsage: true) for OpenAI-compatible providers (such as Ollama, LM Studio, and vLLM), so token metrics are correctly emitted and displayed during streamed sessions. Also adds support for configuring includeUsage: false on the provider to opt out when using non-standard reverse proxies. Fixes #1658.

If includeUsage is not set and an endpoint rejects stream_options with a 400 or 422, the request is retried once without it, a warning points at includeUsage: false, and it stays off for that provider for the rest of the session.

  • Two pastes made back to back no longer fuse in the message sent to the model. The composer showed two tidy placeholders, but they expanded flush against each other at submit, joining the last line of the first paste to the first line of the second. A paste placeholder now goes on its own line wherever it would otherwise touch other text, whether it is appended or spliced in at the caret. Existing whitespace on either side is left as it is. Closes #1373.
  • A plugin whose import never finishes no longer freezes startup. It is skipped after 30 seconds, and the plugins after it still load. Closes #1656.
  • Fix project plugins not being active for the first prompt. Plugins loaded in the background after startup, so the first chat prompt could run before their hooks existed. Startup now waits for .mjs plugins in a trusted directory, including an editor (ACP) session whose directory is already trusted. A .js file is not loaded, and a hook that returns the wrong shape is logged and skipped.
  • Fixed post-tool-use hooks hiding their output from the model when they fail. A hook that exited non-zero was logged for the user and then dropped, so a passing linter or test run reached the model and a failing one never did. Failing output, stdout and stderr both, is now added to the tool result as <hook-output event="post-tool-use" exit="N">. The hook is still observe-only: the tool call succeeds, and a hook killed by a timeout or a signal is still only logged. Closes #1558.
  • A project .env can no longer mark its own folder as trusted. The .env is loaded before the directory trust check, so a repository could ship NANOCODER_TRUST_DIRECTORY=1 in it and skip the security disclaimer. NANOCODER_TRUST_DIRECTORY, NANOCODER_CONFIG_DIR, NANOCODER_MCPSERVERS, NANOCODER_MCPSERVERS_FILE and NANOCODER_CI_TRUST_LEVEL are now only honoured from your shell environment, and a warning names any that a project .env tried to set. Loading a .env also no longer prints a dotenv banner.
  • Two Nanocoder sessions in the same project no longer erase each other's prompt history. Each session wrote its own in-memory list over the shared history file, so the last one to save won and the other session's prompts were lost. A save now merges this session's new prompts into what is on disk. Temp files left behind by a session that died mid-save are cleaned up on the next start.
  • The per-response usage indicator now shows what share of the prompt the provider served from its cache (9.8k cached (82%)), and debug logs report prompt prefix stability per request: whether a turn only appended messages, or whether the system prompt, tool schemas or earlier history changed — and for history, the index of the first rewritten message. Local inference backends only reuse their KV cache for the byte-identical prefix of a request, so a prefix break near the start of a 30k-token context means paying full prefill again; these two measurements make that visible instead of showing up only as slow responses. Groundwork for #1574.
  • Refresh the default models suggested by the provider setup wizard so new configs no longer start on retired or outdated ids. ChatGPT / Codex now defaults to gpt-6.1-sol (gpt-5.3-codex is no longer served), Together AI to deepseek-ai/DeepSeek-V4-Pro-0813 (the undated V4-Pro id was removed from serverless), Poe to claude-sonnet-5.5 (Poe does not serve gpt-5.6-sol), and MiniMax Coding to MiniMax-M3.1-Flash,MiniMax-M3 (M2.7 retires on 2026-11-18).

Other defaults move to each provider's current flagship: OpenAI, GitHub Copilot, API Route, Requesty, OrcaRouter, FutureInfra and Atlas Cloud to GPT-6.1 Sol, Anthropic to claude-sonnet-5-5, OpenRouter to anthropic/claude-sonnet-5.5, Gemini to gemini-3.8-flash, Z.ai and Z.ai Coding to glm-5.3, Thesean to ship-like/claude-opus-5-5, and Ollama to qwen3.8. The Kimi Code template now uses the international endpoint https://api.kimi.ai/coding/v1, and the provider docs show current model ids.

  • Prevent Bash completion from suggesting subcommands when a flag expects an arbitrary value. Closes #1593.
  • Queued-message previews, the session label, the active-editor filename, and session titles no longer wrap onto extra rows when the text is CJK or contains emoji. The shared truncate helper budgeted in UTF-16 code units while its callers pass a column count, and a double-width CJK ideograph or emoji is one code unit but two terminal columns, so a "truncated" line could render at roughly twice its intended width. It now truncates by actual terminal column width via cli-truncate, so a queued message stays on a single row regardless of script. Closes #1531.
  • Long queued-message previews no longer wrap onto a second row. The preview was truncated against the full terminal width, but it renders inside the prompt box, which is narrower once its margin, border and padding are taken off, so the ellipsis or image count spilled onto the next line. It is now budgeted against the box it actually renders in, for both ASCII and CJK text.
  • Fix Windows language server discovery and startup for npm-installed .cmd shims, including local installation paths containing spaces. Closes #1690.
  • Fix quick review for forks: resolve PR numbers across configured remotes and their fork parents, accept explicit GitHub PR URLs, treat renamed repositories as one, and pin gh lookups to github.com. Branch reviews now diff against the upstream base instead of local main, and the result renders as a normal assistant message with the reviewed scope and any omitted diff lines. Extra target arguments are rejected. Part of #1287.
  • Fix a race in the composer where typing, pressing Backspace, or pressing Enter immediately after a paste could silently drop the pasted content or swallow the key. Edits that arrive before the paste is shown are now replayed onto it, and Enter submits what the composer actually holds.
  • Fixed the UI sometimes not re-laying out after a terminal resize. Layout reactivity came from the clamped box width, so any resize that landed inside a clamp (below 44 or above 204 columns) produced no re-render and left the welcome screen, input box, status bar and session selector sized for the old terminal. Width now derives from a reactive raw column count. Closes #1328.
  • Fixed read_file telling the model that a symlinked file was "already in context, unchanged since last read" after the file it points to had changed, for example AGENTS.md linked to CLAUDE.md after CLAUDE.md was edited. The repeat-read check compared the link's own modification time and size, which do not change when the target does. It now compares the target's, so the changed content is returned, and unchanged links still get the short stub. metadata_only still describes the link itself. Thanks to @diyaavirmani. Closes #1834.
  • read_file no longer answers "already in context" for a file the current history never saw. Each ACP chat keeps its own stubs, and ACP /clear, retry, timeline revert, and loading a chat from disk reset them. TUI /resume, /tune, and the config wizard reset them too. Refs #793.
  • Fixed plan mode running tools it never offered. The mode only decided which tools the model was shown, so a model that named a hidden tool anyway (for example write_file or execute_bash in plan mode) reached the approval step, where an alwaysAllow entry or a client's Allow let it run. The interactive loop, nanocoder run and ACP now refuse any registered tool that was not offered for the turn - by mode, tune profile or disabled list - and tell the model it is not available. This also covers MCP tools that plan mode hides because they are not read-only.
  • /rename now saves the new name to the session, so autosave no longer puts the derived title back and /resume shows the name you chose. Renaming before the first message names the session once it is created. Closes #1637.
  • Fix /review on a branch showing commits that were already merged upstream. A branch review was diffed against local main, which goes stale, and in a fork even origin/main is only the fork's copy. It now diffs against the upstream base: the pull request's base branch when one exists, otherwise the parent repository's default branch, and local main only as a last resort. When a fork's parent is not a configured remote, the review says so and prints the git remote add upstream command to compare against it.
  • Fixed the Tool approval required message under the plain run runtime suggesting changes that would not help. It always said to re-run with --mode auto-accept, even when the run was already in auto-accept (the default for run), where bash and irreversible file ops still need approval. It now suggests --mode auto-accept only when that mode would have run every blocked call, and always offers --mode yolo. The alwaysAllow hint also named the wrong place: the key is read only from inside the nanocoder object, so the message now shows the exact snippet to add, e.g. {"nanocoder": {"alwaysAllow": ["execute_bash"]}}. The --json report for this outcome now also carries autoAcceptWouldRun, so scripts can tell which flag would unblock the run. Thanks to @diyaavirmani. Closes #1740.
  • Fixed nanocoder run losing its report when a provider request fails partway through, for example with an HTTP 500 or a dropped connection after a tool had already run. With --json, stdout was empty, so CI got no report and no record of the tool calls that had run, and in both --json and text mode the session-end hook never fired. The failure now ends the run with the usual "error" report, including the tool calls captured before it, and the run still exits 1. Any other fatal error after the session has started now also exits through graceful shutdown, so session-end still runs. A run stopped with SIGTERM or Ctrl+C now always exits 1 with its "Aborted" report; before, it exited 0 or 1 at random, and with --json the report could be cut off. Thanks to @diyaavirmani. Closes #1835.
  • Defer sandbox jail temp directory cleanup until process group termination on cancel/timeout
  • Update dependencies to fix published security advisories. @modelcontextprotocol/sdk moves to 1.32 (which also brings patched express, body-parser and proxy-addr), undici to 8.12, and fast-uri and zod to their patched releases. Development and build tooling (ava, the changesets CLI and the VS Code extension packager) also pick up patched brace-expansion, js-yaml, shell-quote, launch-editor, markdown-it, linkify-it, form-data and source-map-js. One high advisory remains in @azure/msal-node, used only by vsce to package the VS Code extension and not shipped to users; fixing it needs a major upgrade of the packaging tool.
  • Fixed saved sessions disappearing from /resume, --continue and --resume when two Nanocoder processes saved at the same time. The session index (sessions.json) was only locked within one process, so a second terminal, the VS Code extension or a parallel nanocoder run could overwrite it and drop the other session's entry, after which nanocoder --resume last in that project reported "No sessions found." Index updates now take a lock file shared by every process (the same heartbeat lock the memory store uses), and on startup the index is reconciled with the session files on disk: sessions lost this way earlier are listed again, a deleted index is rebuilt, and entries whose session file was deleted are removed from the index (nanocoder storage no longer reports them as missing). Thanks to @diyaavirmani. Closes #1706.
  • The interactive directory-trust prompt now resolves trust through the same isDirectoryTrusted check as --plain and nanocoder daemon start, so the matching rule can no longer drift between entry points. Also corrects the --trust-directory warning, which now names nanocoder daemon start alongside nanocoder run. Closes #1339.
  • Shell completions now offer the review, verify and skills subcommands and the --prompt-file, --mouse and --no-mouse options, which the CLI accepted but never tab-completed. A misspelled verify such as nanocoder verfy now gets a "Did you mean" suggestion too.
  • Fix the composer input box clipping its left border, prompt marker, and placeholder start on terminals narrower than 40 columns, by clamping the box's width floor to the actual terminal width.
  • update test - mcp-client-spec.ts
  • Remove unused logging dependencies to reduce production install size.
  • Fixed /stats dropping arrow-key presses and freezing on a range tab. The range stepped from the value captured in the input handler's closure, but Ink re-registers that handler in a passive effect that runs after the frame is painted — so a press arriving before the effect landed was dispatched with the previous render's range, recomputed the tab it had already moved to, and wedged there until another key broke the tie. The range now steps from the value React holds.
  • Fix the live streaming preview losing its bound when a short line is followed by one huge line. computeStreamingTail snapped its slice start back to the nearest preceding newline to avoid a partial leading line, but that search had no lower bound: a message like Intro\n followed by a minified bundle or a big JSON blob snapped the start back past the whole bound, so the tail became the entire message and every streaming update re-wrapped all of it. The snap-back now only applies when the newline is within one line's width of the raw start, accepting a partial leading line rather than discarding the bound.
  • Fix the live streaming preview growing unbounded on a response with one very long line and no newlines. computeStreamingTail snapped its slice start back to the nearest preceding newline to avoid a partial leading line, but with no newline at all it snapped all the way to 0, discarding the tail bound entirely. It now falls back to the unsnapped tail start instead, accepting one truncated leading line rather than rendering the whole growing message. Closes #1555.
  • After a bash cd, the string_replace approval preview and the ACP edit diff read the file in that directory. They used to open the same relative path from the directory nanocoder was started in. Closes #1634.

The preview labels the file with its path from the project root (or its absolute path outside it), so an edit in sub reads as sub/<file>.

  • Fixed the sub-agent transcript appending "..." to every tool result. The view sliced each result to 100 characters and added the ellipsis unconditionally, so a short result like OK rendered as OK... and implied output that was never truncated. The ellipsis now appears only when the content is actually past the limit, matching the guarded pattern in the git-commit tool card. Closes #1408.
  • --help and -h now print usage and exit 0 for nanocoder daemon, every daemon subcommand, nanocoder skills and nanocoder verify, instead of reporting an unknown flag. A daemon subcommand given --help no longer runs, so nanocoder daemon install --help no longer installs the auto-start unit.
  • File tools now accept an absolute path that reaches the project through a symlink, such as the real path of a project opened through a symlink, or /tmp vs /private/tmp on macOS. Paths that really sit outside the project are still rejected. Closes #1636.
  • Reject malformed proposal indexes such as 1oops, 1.5, 1., +1, and 1e2 in /memory accept instead of silently saving proposal 1. Show the invalid input and valid range when rejecting it. Closes #1589.
  • Fix the untrusted-directory error from nanocoder run saying that NANOCODER_TRUST_DIRECTORY=1 bypasses the disclaimer "for this run". Only --trust-directory is per-run; the environment variable saves the directory to trustedDirectories, so a CI job that exported it believing it was temporary permanently trusted every checkout it touched. The text and --json messages now say --trust-directory applies to this run only and NANOCODER_TRUST_DIRECTORY=1 trusts the directory permanently, matching the daemon's message and the docs. Closes #1660.
  • The interactive TUI now honours NANOCODER_TRUST_DIRECTORY=1 the same way --plain, ACP and nanocoder daemon start do: it skips the directory trust disclaimer and records the folder as trusted, instead of showing the prompt anyway. As before, a project .env cannot set this variable, so only your own shell environment can grant trust this way.
  • The interactive TUI now runs React's production build instead of the development one, which cuts the CPU used while a reply streams. NODE_ENV is set to production only while the TUI is loaded and is unset again right after, so commands run by tools still get the environment of the launching shell, and an explicit NODE_ENV is left as it is. nanocoder --version no longer loads React. Closes #1773.
  • Fixed a race in the one-time usage.json legacy migration: two processes migrating at once could let the loser paste its stale copy over the winner's fresher file (via the renameSync copy fallback). Migration now publishes with a temp file plus an atomic hard link that raises EEXIST when a peer won, adopting the existing file untouched instead of overwriting it.
  • Fix @file attachments in the VS Code chat panel sending the saved file instead of what is in the editor. Attachments were read from disk, so unsaved edits never reached the model. The open editor's text is now used when the file is open (with the same size cap and binary check), falling back to the file on disk otherwise. Closes #1723.
  • Fix the VS Code extension's New Chat bringing back the conversation it was meant to leave. If New Chat was clicked while a session was still being created, the creation finished afterwards and wrote the old session id back, so the next prompt continued the abandoned conversation. A session creation that is overtaken by New Chat, a reconnect of the agent process, or a resumed session is now discarded, and anything waiting on it gets the current conversation's session instead. Closes #1716.
  • The VS Code extension's New Chat now sends session/close for the conversation it leaves, when the agent supports it. Previously the old session stayed open in the agent, holding its timeline lock and history in memory until the agent process exited. A failed close is logged to the Nanocoder output channel and does not block the new chat.
  • Fix the VS Code extension using the nanocoder.cwd setting for some operations but the first workspace folder or the process directory for others, so the extension could disagree with the folder the CLI was running in. The extension client, the chat view, process manager and activation now share one resolver (nanocoder.cwd, then the first workspace folder, then the home directory). @ mention search uses only the configured or workspace folder and never crawls the home directory.
  • Fixed the welcome screen's menu wrapping apart on narrow terminals, with each command landing on its own line. The menu now steps down to the short version, or is left out, when its rows would not fit the terminal width, the same way it already does for height. Closes #1434.
  • On Windows, cancelling or timing out execute_bash or a custom tool now stops the program the command started, not just its cmd.exe wrapper, so a dev server no longer keeps holding its port. Closes #1657.
  • Fixed pnpm run build and pnpm run build:vscode failing on native Windows. The build scripts used POSIX-only mkdir -p, cp and chmod commands, which cmd.exe cannot run, so the voice plugin output and contributors.json were never copied into dist/ and the VS Code extension's assets/ folder was never created. The copy and permission steps now live in scripts/post-build.js, and the assets/ folder is created with Node's fs, with unchanged behavior on macOS and Linux. A new Build (Windows) CI job runs pnpm run build on windows-latest to prevent regressions.
  • Formatter and hook commands written the documented way, like npx biome format --write "$FILE", now work on Windows. cmd.exe expands %FILE%, not $FILE, so the formatter used to receive the literal text $FILE and the file was left unformatted without any error. On Windows, references to the path variables ($FILE, $NANOCODER_FILE, $NANOCODER_CWD, $NANOCODER_SESSION_CWD, and their ${VAR} forms) are now rewritten to %VAR% before the command runs, and quoted when they were left unquoted. The path still reaches the shell only through the environment, never spliced into the command. A path that cmd.exe cannot quote safely (one containing " or a line break) makes the command fail to run instead of running it. POSIX behavior is unchanged. Closes #1688.
  • Keep XML tool calls with = in argument values instead of dropping bash assignments or code edits. Preserve tool-call examples inside JSON, function-tag, and XML arguments, including template braces and fenced markdown, without executing them or erasing their content. Malformed attribute-style tags in the call structure are still rejected. Closes #1630.

If there are any problems, feedback or thoughts please drop an issue or message us through Discord! Thank you for using Nanocoder.

Contributors

This release shipped thanks to @aakash-env, @addyCooks, @akramcodez, @AniketR10, @AryanNandanwar, @DarshanCodes09, @DennyHo0917, @Devesh36, @Dhirenderchoudhary, @dhruvil-codes, @diyaavirmani, @ducanhnguyen223, @Elioooon, @insuffer, @karthikponna, @macjayz, @MayurK-cmd, @mikemikimike, @niukanen1, @ozenyunus35, @Piyushrathoree, @puri-adityakumar, @rishu685, @roberttidball, @rohanshrma222, @RONAK-AI647, @Shreya657, @soumojit-D48, @Srinidhi444, @stanleys12, @theluckystrike, @TheNetherWatcher, @Thibaultjaigu, @uxidev, @Vishesh-Verma-07, @will-lamerton, @yetuge, @yunuschoudhurywork-lang and @Zaygal.

First-time contributors: @DarshanCodes09, @DennyHo0917, @Devesh36, @dhruvil-codes, @diyaavirmani, @ducanhnguyen223, @Elioooon, @insuffer, @karthikponna, @MayurK-cmd, @ozenyunus35, @roberttidball, @Srinidhi444, @stanleys12, @theluckystrike, @TheNetherWatcher, @uxidev, @Vishesh-Verma-07, @yetuge and @Zaygal. Welcome, and thank you!

Installation

npm install -g @nanocollective/nanocoder

Usage

nanocoder

Full Changelog: v1.31.0...v1.32.0