Repository navigation
What's Changed
-
Nanocoder's ACP agent now honours the
mcpServersan editor sends onsession/new,session/loadandsession/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.initializenow reportsmcpCapabilities(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/revertand_nanocoder/retryTurn. The bare names still work as deprecated aliases and log a one-time notice, and will be removed in a later release.initializenow lists the exact method and notification names underagentCapabilities._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_optionaffects 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/resumeandsession/loadrestore 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 advertisesagentCapabilities._meta.nanocoder.perSessionModel. -
ACP clients can now exempt a session from retention with the new
_nanocoder/pinSessionextension method (sessionId,pinned). NeitherretentionDaysnormaxSessionsdeletes a pinned session, and pinned sessions do not count towardmaxSessions;session/deletestill removes them.session/listentries report the mark as_meta["nanocoder/pinned"], andinitializereports the effectiveretentionDaysandmaxSessionsas_meta["nanocoder/retention"]. -
ACP clients can now turn tools off for a single session. Send
_meta: {"nanocoder/disabledTools": ["browser"]}onsession/new,session/loadorsession/resume, for example to stop Nanocoder's built-in browser when the editor provides its own. The names are added to the user's owndisabledTools, 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_metareports 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/subagentnotification as every run starts, progresses and ends (completed,failedorcancelled), with a stablechildRunId, theparentRunId(empty for a child of the turn), the parentagenttool call, the subagent's name, progress counts and its result or error. A new_nanocoder/cancelSubagentrequest cancels one run bychildRunIdwithout ending the turn: the run reportscancelled, the parentagentcall 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_metaasnanocoder/childRunId. Both are listed inagentCapabilities._meta.nanocoder. Clients that ignore the new notification see the same behaviour as before. -
Add a read-only
_nanocoder/timeline/readACP method that returns a file's before-image for a timeline checkpoint (text,createdorunavailable), and reportcreatedFilesand a per-promptturnIdin_nanocoder/timeline/listentries.truncateToMessageIndexchanges with every model round-trip, soturnIdis what groups the checkpoints of one prompt; checkpoints recorded before this change have noturnId. -
Clients can now check and grant directory trust without
--trust-directory.nanocoder trust --status [--json]reports whether the folder is trusted (trusted,not-trusted,changedorneeds-re-approval), what it will run and its fingerprint, and loads nothing from the folder, including its.envand 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 --acpnow exits with code3when it refuses an untrusted folder, instead of1, 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.envcan no longer setXDG_CONFIG_HOMEorAPPDATA, 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 providerswizard. Selecting it fills in the base URL (https://futureinfra.ai/v1/ai) so only an API key and a model name (for exampleopenai/gpt-4o-mini) are needed. -
Added an agent evaluation harness under
benchmarks/agent/, run withpnpm run test:agent-eval. It measures pass/fail, steps, tokens and cost per task by shelling out tonanocoder --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 outsidepnpm 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_prgainsdiff,comment,review,checksandlogsactions (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-directorybypasses this for one run), and its reviewer can never comment, review or open PRs itself, whateveralwaysAllowsays. -
The per-project daemon can watch GitHub Actions for failures (
ciWatchin your global preferences, requires theghCLI), run a read-onlyverify-ci-investigatorsubagent, post the diagnosis to the branch's PR, and send OS notifications. Skills can react to failures too with a newci.job.failedtrigger (optionalbranchesfilter). -
nanocoder daemon start --trust <comment-only|auto-fix|full-commit>(ornanocoder.verify.trustLevelinagents.config.json) lets the investigator fix the failure in an isolated clone:auto-fixpushes a new branch and opens a draft PR;full-commitpushes 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, andfull-commitconfirmation andciWatchare only read from your global preferences, never from a project file.
Safety notes:
- At
auto-fixandfull-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 ownorigin. verifyand CI investigations always use the built-inverify-pr-reviewandverify-ci-investigatoragents, 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.trustLevelofauto-fixorfull-commitinagents.config.jsonneeds confirmation atdaemon start; unattended starts, including the installed service, fall back tocomment-only.--trust=<level>is accepted,daemon statusshows 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 totrueinagents.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/commitprompt), falling back tochore: 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
tscoutput, keeps each failing test's title, location, assertion and expected/received diff, and narrows the original command to the first failing test (for examplenpm 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;!commandinput 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
browsertool (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 withnpx playwright-core install chromium(add--with-depson 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
/helpwith 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_replaceanddiff_editsearches, 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, beforepost-tool-usehooks 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 athttps://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 bearerAuthorizationheader. Keys containing spaces, line breaks or control characters are rejected in the form. - Added
?key=valueinline 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--flaglong forms.
Behavior:
thresholdvalues outside 50–95 and unparseable values are ignored (fail open: the command runs un-gated).auto-compactis 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=barkeys 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=1literally).
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
--providerand--modecan 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 pressingCtrl+Rrepeatedly, accept and populate the input field withEnter(preserving any attachments or placeholders), or cancel and restore their in-progress draft withEsc. The reasoning trace toggle shortcut has moved fromCtrl+RtoCtrl+N(Ctrl+Gis voice push-to-talk andCtrl+Tstays 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. Usenanocoder storage --format jsonfor 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
trustedDirectoriesentries 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.
/voiceturns it on, with push-to-talk onCtrl+Gor 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 pressingCtrl+Ginterrupts 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/homebrewon 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 ACPprompt()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 vendoredhighlight.jsbundle (16 common languages, the same static-file approach asmarked.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/loadandsession/resumeresponses list, under_meta["nanocoder/approval"].alwaysAllow, the tools offered in the session that analwaysAllowlist (the top-level one or an MCP server's) lets run without asking, so a client can tell thatnormalmode will not ask for them. How approvals are decided is unchanged. Closes #1910. -
ACP clients can now close a session. Nanocoder had advertised
session/closesince 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 --acpnow refuses a directory you haven't trusted, the same checknanocoder runmakes, before it loads that directory's project config (providers, hooks, MCP servers). Trust the directory by runningnanocoderinteractively there once, setNANOCODER_TRUST_DIRECTORY=1, or pass--trust-directoryfor one run. The VS Code extension passes--trust-directoryonly 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
failedonly 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 ascompleted. The status now also follows theisErrorflag thatprocessToolUsealready sets for these, which also stops awrite_planthat failed validation from counting as a written plan. - Fix the
/helptext of the VS Code chat showing a garbled character before each command description.source/acp/acp-agent.tscontained 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/loadnow fails withSession not found on disk: <id>for a session ID that is neither in memory nor on disk, the same errorsession/resumealready 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
yoloorauto-acceptback tonormalstill 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 toplan) is refused instead of run. A call that is already running is not interrupted. Closes #1857. - ACP no longer falls back to
auto-acceptwhen a mode is wrong.session/set_modenow 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. Asession/set_modethat succeeds is confirmed with acurrent_mode_updatenotification, 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 theclientInfoit sends ininitialize, keeps its wording unchanged; every other client, including one that sends noclientInfo, 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 andAGENTS.mdused the directory the agent process was spawned in, so when an editor started the agent elsewhere (for example with the VS Codenanocoder.cwdsetting, 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 owncwd, acdin 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: ayoloorauto-acceptsession is no longer prompted inside a subagent for tools it would not be prompted for at the top level, and aplansession'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.
ArchitectReviewPromptwas missing the shared left-edge wrapperPlanReviewPrompt,FileExplorer,IdeSelectorandModalSelectorsalready 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.autoSaveisfalse. 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.autoCommitcommitting other files when the edited file's name looks like a git glob. The path was passed togit add,git diff --cachedandgit commitas a pathspec, so an agent edit to[ab].txtalso matcheda.txtandb.txtand 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_bashno longer hangs on a command that starts a background job. A command such asnpm run dev &orsleep 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_bashonly prefixed its result withError: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 setsToolResult.isErroron a non-zero exit and the compact display branches on that flag, showing⚒ execute_bash failedinstead. Together with the earlier--plain/--json/ACP fix, this closes #1371. - Fixed commands started by
execute_bashrunning 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 withcmd &: SIGTERM first, then SIGKILL after 2 seconds. A timeout or cancel is now reported only once the command has actually stopped, so ananocoder runcan 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_bashon Windows no longer mangles double-quoted arguments. The command was handed tocmd /cas an ordinary argv entry, so Node re-quoted it and escaped every inner"as\", whichcmd.exedoes not understand.echo "hello world"printed the backslashes,git commit -m "fix the bug"failed withpathspec 'the' did not match, andnode -e "console.log(1+1)"exited 0 with no output, so the agent believed it had worked. It now spawnscmd.exethe way custom tools already do (/d /v:off /s /c "<command>"with verbatim arguments), and a Windows-only spec coversecho,node -e,git commit -mandgit 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/updatecommand 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
--mdoefrom silently leavingrunin 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_diffandgit_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 installnow warns when the project isn't trusted yet. The installed service runsnanocoder daemon startunattended on a restart-on-failure policy, anddaemon startrefuses 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: runnanocoderthere 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-directoryfailing withUnknown flag: "--trust-directory".cli.tsxalready 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 starttrust 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
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
😀abcin 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
/expandcuts 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.
/explorerno 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(includingnanocoder --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 --helpornanocoder 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_urlnow 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 examplelocaltest.meor169.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_filecache, splitlineson demand, and sweep expired TTL entries on insert. Refs #1781. - Fixed
file_opdeleting or overwriting files without a prompt inauto-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 wayrmthroughexecute_bashdoes, 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 inalwaysAlloware unchanged. Non-interactivenanocoder runstops withTool approval requiredon such a call, as it already does for bash. Thanks to @diyaavirmani. Closes #1683. - Fixed
TimelineManagerpruning an active session whose directorymtimeMswas older thanMAX_TIMELINE_SESSION_AGE_MS. Each session now holds a per-process lockfile at.nanocoder/timeline/<sessionId>/.lock, published atomically via a temp file plus hardlinkso concurrent readers never observe a half-written lock;pruneStaleSessionsprobes 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-historystarting 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.jsis no longer executed as the Nanocoder CLI. The local development fallback now requires the workspace folder'spackage.jsonto name@nanocollective/nanocoder(#1725); other folders are skipped and discovery continues to the installed CLI. - Fixed the
/scheduledescription 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 than0, and a wildcard minute with a step hour, now return the raw expression instead of a wrong sentence. - Keep
globalThis.processintact whenfetch_urlor 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 nextprocessread crashed the app through the fatal-error handler. The logging config, logger provider and shutdown manager now use a module-levelprocessreference, 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
loadAppConfigstripping the.sourcefield when unwrapping MCP server configs, which silently disabledvalidateProjectConfigSecurity'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,threeused to submitonetwothreewith 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_logignoring itsbranchargument. It always returned the commits of the checked-out branch while labelling them with the requested one, so asking for thefeaturebranch frommainshowedmain's history under afeatureheader. The branch is now passed togit 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/taskslist,start,doneandcompletesubcommands. In the VS Code extension,/modeland/providernow 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_fileof 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, andgh --versioncalls; 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, andgit_prstill requires the gh CLI. Thanks to @Vishesh-Verma-07. Closes #1819. - Lazy-load
cli-highlight,highlight.js(193 language modules) andparse5until syntax highlighting is actually needed.highlight()is now wrapped in a synchronous lazy-loader so startup commands like--version, idle TUI sessions, and--plainruns that never display a fenced code block or file preview no longer pay the startup import and language registration cost. Closes #1776. - Load the
wspackage lazily so the VS Code WebSocket server's dependencies stay out of the startup module graph unless--vscodeis used. Previously every boot — interactive,run, and the daemon — loadedwsand 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_LEVELprinted log lines to stdout instead, which brokenanocoder run --jsonoutput 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_diagnosticsnow says the server crashed (and whether it is restarting) instead of claiming the file type is unsupported. Closes #1515. lsp_get_diagnosticsno 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 malformedfile://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_diagnosticsnow only accepts files inside the project, likeread_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.tsor 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.maxFpspreference (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_fileorstring_replaceedit, the empty prompt now suggests a relevant follow-up:/commitwhen 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 configuringincludeUsage: falseon 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
.mjsplugins in a trusted directory, including an editor (ACP) session whose directory is already trusted. A.jsfile is not loaded, and a hook that returns the wrong shape is logged and skipped. - Fixed
post-tool-usehooks 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
.envcan no longer mark its own folder as trusted. The.envis loaded before the directory trust check, so a repository could shipNANOCODER_TRUST_DIRECTORY=1in it and skip the security disclaimer.NANOCODER_TRUST_DIRECTORY,NANOCODER_CONFIG_DIR,NANOCODER_MCPSERVERS,NANOCODER_MCPSERVERS_FILEandNANOCODER_CI_TRUST_LEVELare now only honoured from your shell environment, and a warning names any that a project.envtried to set. Loading a.envalso 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 todeepseek-ai/DeepSeek-V4-Pro-0813(the undated V4-Pro id was removed from serverless), Poe toclaude-sonnet-5.5(Poe does not serve gpt-5.6-sol), and MiniMax Coding toMiniMax-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
.cmdshims, 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
ghlookups to github.com. Branch reviews now diff against the upstream base instead of localmain, 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_filetelling the model that a symlinked file was "already in context, unchanged since last read" after the file it points to had changed, for exampleAGENTS.mdlinked toCLAUDE.mdafterCLAUDE.mdwas 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_onlystill describes the link itself. Thanks to @diyaavirmani. Closes #1834. read_fileno 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_fileorexecute_bashin plan mode) reached the approval step, where analwaysAllowentry or a client's Allow let it run. The interactive loop,nanocoder runand 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. /renamenow saves the new name to the session, so autosave no longer puts the derived title back and/resumeshows the name you chose. Renaming before the first message names the session once it is created. Closes #1637.- Fix
/reviewon a branch showing commits that were already merged upstream. A branch review was diffed against localmain, which goes stale, and in a fork evenorigin/mainis 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 localmainonly as a last resort. When a fork's parent is not a configured remote, the review says so and prints thegit remote add upstreamcommand to compare against it. - Fixed the
Tool approval requiredmessage under the plainrunruntime 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 forrun), where bash and irreversible file ops still need approval. It now suggests--mode auto-acceptonly when that mode would have run every blocked call, and always offers--mode yolo. ThealwaysAllowhint also named the wrong place: the key is read only from inside thenanocoderobject, so the message now shows the exact snippet to add, e.g.{"nanocoder": {"alwaysAllow": ["execute_bash"]}}. The--jsonreport for this outcome now also carriesautoAcceptWouldRun, so scripts can tell which flag would unblock the run. Thanks to @diyaavirmani. Closes #1740. - Fixed
nanocoder runlosing 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--jsonand text mode thesession-endhook 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, sosession-endstill 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--jsonthe 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/sdkmoves to 1.32 (which also brings patchedexpress,body-parserandproxy-addr),undicito 8.12, andfast-uriandzodto their patched releases. Development and build tooling (ava, the changesets CLI and the VS Code extension packager) also pick up patchedbrace-expansion,js-yaml,shell-quote,launch-editor,markdown-it,linkify-it,form-dataandsource-map-js. One high advisory remains in@azure/msal-node, used only byvsceto 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,--continueand--resumewhen 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 parallelnanocoder runcould overwrite it and drop the other session's entry, after whichnanocoder --resume lastin 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 storageno longer reports them as missing). Thanks to @diyaavirmani. Closes #1706. - The interactive directory-trust prompt now resolves trust through the same
isDirectoryTrustedcheck as--plainandnanocoder daemon start, so the matching rule can no longer drift between entry points. Also corrects the--trust-directorywarning, which now namesnanocoder daemon startalongsidenanocoder run. Closes #1339. - Shell completions now offer the
review,verifyandskillssubcommands and the--prompt-file,--mouseand--no-mouseoptions, which the CLI accepted but never tab-completed. A misspelledverifysuch asnanocoder verfynow 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
/statsdropping 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.
computeStreamingTailsnapped its slice start back to the nearest preceding newline to avoid a partial leading line, but that search had no lower bound: a message likeIntro\nfollowed 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.
computeStreamingTailsnapped 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, thestring_replaceapproval 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
OKrendered asOK...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. --helpand-hnow print usage and exit 0 fornanocoder daemon, everydaemonsubcommand,nanocoder skillsandnanocoder verify, instead of reporting an unknown flag. Adaemonsubcommand given--helpno longer runs, sonanocoder daemon install --helpno 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
/tmpvs/private/tmpon macOS. Paths that really sit outside the project are still rejected. Closes #1636. - Reject malformed proposal indexes such as
1oops,1.5,1.,+1, and1e2in/memory acceptinstead of silently saving proposal 1. Show the invalid input and valid range when rejecting it. Closes #1589. - Fix the untrusted-directory error from
nanocoder runsaying thatNANOCODER_TRUST_DIRECTORY=1bypasses the disclaimer "for this run". Only--trust-directoryis per-run; the environment variable saves the directory totrustedDirectories, so a CI job that exported it believing it was temporary permanently trusted every checkout it touched. The text and--jsonmessages now say--trust-directoryapplies to this run only andNANOCODER_TRUST_DIRECTORY=1trusts the directory permanently, matching the daemon's message and the docs. Closes #1660. - The interactive TUI now honours
NANOCODER_TRUST_DIRECTORY=1the same way--plain, ACP andnanocoder daemon startdo: it skips the directory trust disclaimer and records the folder as trusted, instead of showing the prompt anyway. As before, a project.envcannot 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_ENVis set toproductiononly 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 explicitNODE_ENVis left as it is.nanocoder --versionno longer loads React. Closes #1773. - Fixed a race in the one-time
usage.jsonlegacy migration: two processes migrating at once could let the loser paste its stale copy over the winner's fresher file (via therenameSynccopy fallback). Migration now publishes with a temp file plus an atomic hard link that raisesEEXISTwhen a peer won, adopting the existing file untouched instead of overwriting it. - Fix
@fileattachments 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/closefor 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.cwdsetting 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_bashor a custom tool now stops the program the command started, not just itscmd.exewrapper, so a dev server no longer keeps holding its port. Closes #1657. - Fixed
pnpm run buildandpnpm run build:vscodefailing on native Windows. The build scripts used POSIX-onlymkdir -p,cpandchmodcommands, whichcmd.execannot run, so the voice plugin output andcontributors.jsonwere never copied intodist/and the VS Code extension'sassets/folder was never created. The copy and permission steps now live inscripts/post-build.js, and theassets/folder is created with Node'sfs, with unchanged behavior on macOS and Linux. A newBuild (Windows)CI job runspnpm run buildonwindows-latestto prevent regressions. - Formatter and hook commands written the documented way, like
npx biome format --write "$FILE", now work on Windows.cmd.exeexpands%FILE%, not$FILE, so the formatter used to receive the literal text$FILEand 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/nanocoderUsage
nanocoderFull Changelog: v1.31.0...v1.32.0