---
title: Toolchain Updates (background checks + native-installer notifications)
---

# Toolchain Updates (background checks + native-installer notifications)

## What it is

Omniscio periodically checks whether the CLI tools it depends on (Claude Code, Codex, gh, git, …) have newer versions available, then makes it easy to **update each one yourself, outside Omniscio**. The core model (2026-06-29): **every recommended tool you have installed gets a one-click Update that opens a normal terminal running that tool's own update command** — Omniscio installs nothing in-place for this path. The command is derived from how each tool was installed (`TOOL_PROVENANCE`): npm tools → `npm install -g …`, winget tools (`git`, `github-cli`) → `winget upgrade …`, Go tools → `go install …@latest`, GitHub-release tools → their own installer, and **Claude Code → `claude update` against the actual binary Omniscio runs** (or npm, if that's how it was installed). Claude Code used to sit in the silent-npm bucket, which ran `npm install -g` against a copy that often wasn't even on your PATH — so the Claude Code you actually use never moved. That was the bug; it now updates the real binary, in a terminal you trigger. One convenience is kept for the pure-npm tools (`playwright`, `agentmail`, `repomix`): the **Auto-update Tools** toggle (off by default) silently `npm install -g`s them in the background when you turn it on, since npm needs no admin and that path has always worked. Every other tool — `claude-code`, `codex`, `git`, `github-cli` and the rest of the notify-only list — is never silently updated; it always notifies and opens a terminal. A second toggle, **Tool update reminders** (`toolUpdateNotificationsEnabled`, default off), is the master switch for the proactive method — the scheduled background version check AND the single aggregated "N tools can be updated" nudge. Leave it off (the default) and Omniscio won't nudge you; the one-click **Update** buttons and the Connected Tools panel's on-demand "Check for updates" stay available so you can update by hand anytime. The scheduled check runs when EITHER this toggle or **Auto-update Tools** is on (so the silent-npm path keeps its detection with reminders off); when only the update-card toggles (below) are on it still runs, scoped to the tools those cards cover; only with all four off does Omniscio stop background-checking entirely. Turn it on to opt into the proactive check + toast. Periodic checks run at startup + every 6 hours on a fixed cadence (module constants, not user-facing settings).

## Where to find it

Everything lives in **Settings → Connected Tools**: the **Auto-update Tools**, **Tool update reminders**, **Update alerts for Claude Code and Codex** and **Update alerts for your other tools** toggles (under **Update settings**), a **Connected Tools** panel carrying each tool's one-click **Update** button and an on-demand **Check for updates**, and a **Developer** section with **Run check now**, **Clear cache**, and **Simulate update**. The aggregated **N tools can be updated** nudge opens the Tools view, which is where those **Update** buttons live.

## How it behaves

### How to use it

1. **Default behavior** — nothing to configure. `autoUpdateTools` is off by default; turn it on and the pure-npm tools (`playwright`, `agentmail`, `repomix`) install silently in the background. The notify-only tools (`claude-code`, `codex`, `git`, `github-cli` and the rest) have **no silent path** — they need a terminal (and, for winget/brew tools, sometimes admin), so they're never auto-installed regardless of `autoUpdateTools`; each surfaces a one-click **Update** instead, plus an inbox card when it falls behind (below). Whether anything _toasts_ is governed by the master kill-switch below: out of the box (`toolUpdateNotificationsEnabled` off) updates stay quiet and only surface in Settings → Connected Tools and the Tools view.
2. **Silent installs are opt-in.** With **Auto-update Tools** off (the default), npm updates show an aggregate info toast with **Update** + **Review** buttons instead of installing silently — but only when notifications are on (see step 3) — and the npm trio gets update cards like every other tool. Turn it on in Settings → Connected Tools to install them silently instead. Native-bucket behavior is unaffected by this toggle: it never had a silent install path, and its toasts remain gated by `toolUpdateNotificationsEnabled` either way.
3. **Default is silent — opt into popups.** Out of the box, Omniscio suppresses every "update available" toast (npm aggregate AND per-tool native). Background detection still runs — updates appear in Settings → Connected Tools where you can install them by hand — but nothing pops up to interrupt you. To opt in, Settings → Connected Tools → toggle **Tool update reminders** on. Independent of `autoUpdateTools`: with auto-update on, npm tools still install silently in the background regardless of this toggle; flipping notifications on just adds the "update available" toasts back for the cases where auto-update can't act (npm subset when `autoUpdateTools` is off, native subset always).
4. **Update a tool in a terminal.** When a notify-only tool is behind, or when you click **Update** on any tool in the Tools view, Omniscio opens a detached terminal running that tool's own update command: `claude update` for Claude Code (against the real binary Omniscio runs), `winget upgrade Git.Git` / `winget upgrade GitHub.cli` (Windows) or `brew upgrade git` / `brew upgrade gh` (macOS) for the native tools, and `npm install -g …` / `go install …@latest` for the rest; on Linux the native tools fall back to opening the release page in your browser. A hand-installed tool (RTK, gog) opens its download page instead. Bun is updated through whatever installed the copy on your PATH, because it arrives from npm or its own installer as often as from winget: an npm install runs `npm install -g bun`, a winget one `winget upgrade Oven-sh.Bun`, a Homebrew one `brew upgrade bun`, and anything else Bun's own `bun upgrade`. The terminal stays open (`cmd.exe /k`) so you can read any errors directly. The next periodic check picks up the new version and clears the nudge. Every **Update** button says what happened: an update for that tool already running, a download page opened, or nothing could open.
5. **Recover from EBUSY.** If a silent npm update (`playwright`, `agentmail`, `repomix`) runs while another process holds a lock on the tool's files, npm fails with `EBUSY: resource busy or locked`. Omniscio's `INSTALL_ERROR_PATTERNS` safety net translates that to the friendly message "A process is using this tool. Close any running sessions or terminals using it, then try again." (tool-agnostic) instead of showing raw npm output. Close the offending session/terminal and retry from Settings → Connected Tools.
6. **Developer tools.** Settings → Connected Tools → expand **Developer** to run a manual check, clear the update cache, or simulate a fake update without waiting for a real remote release.

### Update alerts for Claude Code and Codex

When a real check finds Claude Code or Codex CLI behind its latest release, Omniscio puts one durable card per tool in your inbox — "Claude Code needs an update", naming the installed and latest versions — with a one-click **Update now**. The button runs that tool's own updater in a visible terminal on your computer, exactly what the **Update** button in Settings → Connected Tools does; from your phone it still runs on the computer, and the card says so. Omniscio never starts the update itself. Each new version is announced once: a card you dismiss stays quiet for that version, a newer release raises a fresh card, and the card withdraws on its own once a check proves the tool current — clicking never removes it, so a failed update leaves it in place for another try. A tool that is offline, mid-install, not installed, or whose version cannot be read raises nothing and withdraws nothing. The cards are on by default and independent of **Tool update reminders**; Settings → Connected Tools → **Update alerts for Claude Code and Codex** turns them off, and a brand-new install is not shown them during its first day. If the update reports a file in use, close running sessions and click again. Developer → **Simulate update** for Claude Code or Codex raises the real card too (with no version watermark, so it clears on the next real check or when you dismiss it).

### Update alerts for your other tools

The same card covers every other tool Omniscio recommends and can check reliably: Git, GitHub CLI, OpenCode, Pi, portless, agent-browser, pnpm, yt-dlp, Bun, gws, kubectl, RTK and gog — plus Playwright, AgentMail and Repomix while **Auto-update Tools** is off (with it on, Omniscio updates those three itself, so it never cards them). It works exactly like the Claude Code and Codex card: one card per tool per version, a one-click **Update now** that runs the tool's update in a visible terminal, and it clears itself once a check sees the new version. It has its own switch, Settings → Connected Tools → **Update alerts for your other tools** (on by default); turning either switch off leaves the other family exactly as it was. RTK and gog are installed by hand, so their card says so and its button reads **Open download page**. A card only appears when the update can really be delivered: a release with no build for your computer never raises one, and a release for a tool updated through winget or Homebrew (Git, GitHub CLI, yt-dlp, Bun, gws, kubectl) waits a day so the store has it before the card offers it. A copy that came from somewhere else — kubectl bundled inside Docker Desktop, yt-dlp installed with pip — gets no card either, because the winget or Homebrew update the button runs cannot reach it (Docker Desktop updates its own kubectl when Docker Desktop updates); a card already up for such a copy is taken back. Bun is the exception: its button follows whatever installed your copy, so it keeps its card. A tool whose copy on your PATH belongs to a project gets no card and no Update button either: when you run Omniscio from its source code, the project's own pinned pnpm and its Playwright come first on the PATH, so their version is the project's to set and an update would only replace a different copy that nothing runs. Docker, Google Cloud SDK, Tailscale, AWS CLI, Python, FFmpeg and the security tools get no card, because Omniscio cannot read their newest version reliably. Developer → **Simulate update** raises the real card for any of these tools too.

## For agents

### How it works

The service is [toolchain-update-service.ts](../../src/main/services/toolchain/toolchain-update-service.ts). On startup + every 6 hours, `doCheckForUpdates()` fetches the latest version for each tool in `VERSION_SOURCES` (npm registry for the npm tools — `claude-code`, `codex`, `playwright`, `agentmail`, `repomix`, `opencode`, `pi`, `portless`, `agent-browser`, `pnpm`; GitHub releases for the rest — `git`, `github-cli`, `yt-dlp`, `bun`, `gws`, `kubectl`, `rtk`, `gog`). The `isNativeNotify(id)` predicate splits the result into two subsets — its membership comes from `NATIVE_NOTIFY_TOOL_IDS` — now every notify-only tool, fifteen of them — in [src/shared/types/toolchain.ts](../../src/shared/types/toolchain.ts) (re-exported by `src/shared/types.ts`), the single source of truth (NEVER inline that literal — always import). `claude-code` moved here from the npm bucket in the 2026-06-29 universal-update change, so the silent npm path now covers only `playwright`, `agentmail`, `repomix`. When `autoUpdateTools` is on, npm updates flow into `installAvailableUpdates()` from [toolchain-installer.ts](../../src/main/services/toolchain/toolchain-installer.ts) for silent install; native updates and the npm subset (when `autoUpdateTools` is off) go out under a single `TOOLCHAIN_UPDATES_AVAILABLE` push so the toolchain-update store stays the authoritative list. If a silent auto-install THROWS (registry error, permission, network drop), the failed npm updates are re-emitted under that same `TOOLCHAIN_UPDATES_AVAILABLE` push as a manual "update available" (F114) — so a stuck tool never silently reads as current when auto-update couldn't complete; the next 6h detection re-attempts the idempotent install. The renderer hook [useAppToolchainAlerts.ts](../../src/renderer/src/hooks/useAppToolchainAlerts.ts) now raises a single aggregated **N tools can be updated** nudge (no per-tool toast storm — the 2026-06-29 change collapsed the per-tool toasts into one) that opens the Tools view, where each tool's **Update** button invokes `TOOLCHAIN_OPEN_NATIVE_UPDATE` for the terminal path (or `TOOLCHAIN_INSTALL_UPDATES` for the silent npm batch). The master kill-switch `toolUpdateNotificationsEnabled` (default false) is enforced as an early return at the top of `handleToolchainUpdatesAvailable` — when not strictly `=== true`, the hook drops both the npm-aggregate and native per-tool branches before any `showToast` call. The strict `!== true` check (rather than `=== false`) ensures upgrading users whose settings have no persisted value (`undefined`) also default to silent — `getSettings()` returns the raw config without merging `DEFAULT_SETTINGS`, so a missing key is the common case for everyone who installed Omniscio before this default flipped. Completion toasts (`handleToolchainUpdatesCompleted`) are intentionally NOT gated, so the user still sees feedback when an install they triggered finishes. That handler ([toolchain-handlers.ts](../../src/main/ipc/toolchain-handlers.ts) `openToolUpdate()`, generalized in the 2026-06-29 change from the old git/gh-only `openNativeUpdate`) builds the per-tool command with the pure `buildToolUpdateCommand` ([toolchain-update-command.ts](../../src/main/services/toolchain/toolchain-update-command.ts)) off `TOOL_PROVENANCE`, then spawns a detached terminal: on Windows, `cmd.exe /c start "" cmd.exe /k <cmd>` (the `/k` keeps the window open so errors are visible); on macOS, an `osascript` AppleScript that opens Terminal; on Linux, `shell.openExternal(releaseUrl)` for the native tools as the fallback. The tool/package id is validated by the builder's `SAFE_UPDATE_ID` allow-list before it touches the shell string (injection guard). The `updateTool()` IPC handler still services in-place updates for the npm bucket but explicitly rejects native tool ids with `"updateTool no longer supports ${toolId} — use TOOLCHAIN_OPEN_NATIVE_UPDATE"` so future callers can't drift back to the in-place path. A safety-net entry in `INSTALL_ERROR_PATTERNS` translates residual `EBUSY: resource busy or locked` npm errors to "A process is using this tool. Close any running sessions or terminals using it, then try again." (tool-agnostic so it stays accurate regardless of which tool fired the EBUSY) — see [sanitize-install-error.test.ts](../../tests/unit/services/sanitize-install-error.test.ts) for the full pattern catalogue. Periodic checks use `startToolchainUpdateChecker()` on a fixed cadence — the module constants `CHECK_INTERVAL_MS` (6h) and `INITIAL_DELAY_MS` (5min); there are no user-facing timing settings (the former `toolchainCheckIntervalMs` / `toolchainInitialDelayMs` / `toolchainCheckAtStartup` fields were removed as dead in RT-F007 — the service never read them). The scheduled `runPeriodicCheck` does the work `periodicCheckScope()` allows (fire is still recorded for cadence; fail-open on a settings-read error): `'all'` when `autoUpdateTools || toolUpdateNotificationsEnabled`, `'alert-tools'` when only the card toggles (`agentCliUpdateAlertsEnabled`, `otherToolUpdateAlertsEnabled`) are on (a sequential probe of the carded tools whose switch is on that keeps every other tool's cached entry), and `'none'` only when all four are off — so a fully opted-out user stops background-checking while the kept silent-npm path still gets its detection; the manual "Check for updates" path stays ungated. Gate locked by [toolchain-update-service.test.ts](../../tests/unit/toolchain-update-service.test.ts) — the per-flag scope cases prove the `||`. After every real check (and after `clearUpdateForTool` / the post-update reconcile), [agent-cli-update-alert.ts](../../src/main/services/toolchain/agent-cli-update-alert.ts) syncs one inbox card per carded tool — `agent-cli-update-available:<tool>` for the agent CLIs and `tool-update-available:<tool>` for every other updatable tool, each family behind its own switch — raising only when the checker's `hasUpdate` is true and the version is newer than the tool's watermark (the checker holds a release with no build for this OS, and a winget/brew-delivered release for its first 24 hours, flags `managedElsewhere` — offering nothing — for a tool whose probe answered from a project's own `node_modules/.bin` (the `projectLocal` flag set by `resolvesToProjectBin` in [toolchain-exec.ts](../../src/main/services/toolchain/toolchain-exec.ts)), and never offers a winget/brew update for a copy on PATH that store did not install — [toolchain-copy-owner.ts](../../src/main/services/toolchain/toolchain-copy-owner.ts) decides with the update terminal's own `absolutePathOnPath` + `classifyInstallOrigin`, a `selfUpdate` tool excepted, and the entry carries `managedElsewhere`), withdrawing when proven current or `managedElsewhere`, holding on `'unknown'`, and never carding the npm trio while `autoUpdateTools` owns it. Both families' `update-agent-cli` action (an exact **Open download page** row for the hand-installed `rtk`/`gog`) runs through the seam [agent-cli-update-action.ts](../../src/renderer/src/features/alerts/agent-cli-update-action.ts) into the same `TOOLCHAIN_OPEN_NATIVE_UPDATE` handler, which now answers what it opened (`terminal` / `docs` / `none` / `already-running`) and refuses a second launch while [toolchain-update-reconcile.ts](../../src/main/services/toolchain/toolchain-update-reconcile.ts)'s post-launch poll (which covers every updatable tool) is still watching that tool. All three Update buttons — the card, Settings → Connected Tools and the Tools view — turn that answer into a toast through one helper, [tool-update-outcome.ts](../../src/renderer/src/lib/tool-update-outcome.ts). The cards' rules are the [agent-cli-update-alerts contract](../../.claude/memory/contracts/agent-cli-update-alerts-contract.md); the subsystem's parts and traps are the [toolchain-updates map](../../.claude/memory/toolchain-updates-map.md). UI settings live in [ToolchainSettings.tsx](../../src/renderer/src/features/settings/sections/toolchain/ToolchainSettings.tsx) — the Developer section exposes dev-only buttons for Run check now, Clear cache, and Simulate update. Design history: the file is named `toolchain-update-v3.md` because it documents what replaced an earlier "v3" deferred-queue model. v3 queued native-tool updates into a `pendingToolUpdates` settings field and ran a blocking startup splash to install them on next launch; that was removed in favor of opening the installer in-place because winget/brew always need a visible terminal anyway, and the queue + splash machinery had migration and race-condition costs. The current "Option B" model has no queue, no startup splash, and no `pendingToolUpdates` settings field (a one-shot legacy migration in [config-store/migrations/migrate-pending-tool-updates.ts](../../src/main/services/config-store/migrations/migrate-pending-tool-updates.ts) drops the field on read). See [postmortem § Section D](../../.claude/memory/postmortems/toolchain-update-evolution-postmortem.md#section-d--live-only-auto-update-2026-04-24-current-architecture) (merged 2026-05-08 from `toolchain-option-b-refactor-postmortem.md`) for the migration write-up.

## Related

- [notifications-and-silence.md](notifications-and-silence.md) — toolchain toasts route through the same notification stack
- [session-stuck-in-needs-you.md](session-stuck-in-needs-you.md) — when a session won't start, a missing/outdated tool is one of the diagnosis branches
