Skip to content

feat(system): flag stale native deps, not just missing ones (v0.298.0) - #545

Open
vikasprogrammer wants to merge 1 commit into
mainfrom
feat/deps-freshness
Open

feat(system): flag stale native deps, not just missing ones (v0.298.0)#545
vikasprogrammer wants to merge 1 commit into
mainfrom
feat/deps-freshness

Conversation

@vikasprogrammer

@vikasprogrammer vikasprogrammer commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Why

Two issues on the instawp tenant today — /model couldn't list Opus 5, and the opus alias resolved to Opus 4.8 — had one root cause: the box was on claude-code 2.1.216, whose baked model registry tops out at claude-opus-4-8. The string claude-opus-5 doesn't exist in that binary.

The config was fine. What's notable is that Settings → System showed a green "All required dependencies are installed." the entire time. checkDeps() only ever asked is it present?ok is deps.every(d => !d.required || d.installed), so a months-old runtime is fully "ok" by that definition. And the claude dep deliberately carries no package-manager pkg, so it sat outside the install path entirely.

Both boxes are already upgraded by hand (instawp + expresstech → 2.1.220). This closes the gap that let it go unnoticed.

What changed

Freshness — a Dep may now name an npmPkg. checkDepUpdates() (src/edge/deps.ts) asks the npm registry for latest via the abbreviated /latest endpoint using the global fetch — no npm shell-out (it may not be on a systemd unit's PATH), no new dependency — caches for an hour, and flags a behind-version install. checkDeps() stays sync and network-free, so freshness is a layer on top rather than a slower probe; an offline box still gets its full presence report with a per-dep updateError.

GET /api/deps annotates the report (?force=1 re-asks). A stale row goes amber with the published version and, for the owner, an Update button.

UpdatePOST /api/deps/update runs npm install -g <pkg>@latest. Owner-gated, audited system.deps.updated, and never via sudo (sudo npm -g leaves root-owned files in the prefix; a permissions failure surfaces the manual command instead). It prefers the npm sitting beside the resolved binary, so an nvm box can't install into a different node prefix than the one it actually runs from. CLI parity: agent-os deps shows ↑ … → v2.1.220 available, agent-os deps update <bin> applies it.

Fixed — off-PATH resolution. deps.ts resolved binaries with a bare command -v, but claude-cli.ts walks $CLAUDE_BIN → PATH → ~/.local/bin/claude precisely because a launchd/systemd parent ships a minimal PATH. On such a box the panel would report the runtime missing while sessions launched fine. Both now share claudeBinCandidates() and honour the same order — the test caught me resolving PATH first, which would report the version of a binary sessions never run. A dep found only via a fallback is version-probed at its resolved path and badged off PATH.

Verification

  • npm run typecheck, npm run build, cd web && npm run build — clean.
  • npm run test:governance — 159 + 18 + 18 + 29 + 29 + 38 passed, exit 0.
  • New scripts/deps-freshness-test.cjs (38 assertions, isolated home): version comparison, resolution order, the off-PATH case, stale detection, route authz (member/admin 403 on update, owner-only; non-npm and unknown deps refused before any shell-out), and a full update round-trip against a stubbed npm — which also pins the sibling-npm preference.
  • Drove a real server on an isolated home with CLAUDE_BIN pointed at a fake reporting 2.1.100, reproducing the instawp condition exactly: ok: true, outdated: ['claude'], latest: 2.1.220.

Panel in that state: amber "1 dependency is out of date — Claude Code", the row reading 2.1.100 (Claude Code) / v2.1.220 available · npm install -g @anthropic-ai/claude-code@latest, an Update button and an outdated badge, plus the note that running sessions keep their binary until they restart.

🤖 Generated with Claude Code

Settings → System only ever asked "is it installed?". A tenant's box sat on
claude 2.1.216 for weeks — /model couldn't list Opus 5 and the `opus` family
alias resolved to 4.8 — while the panel showed a green "All required
dependencies are installed" the whole time.

Deps may now name an `npmPkg`. `checkDepUpdates()` asks the npm registry for
`latest` (abbreviated /latest endpoint via global fetch — no `npm` shell-out,
no new dep), caches for an hour, and flags a behind-version install.
`checkDeps()` stays sync and network-free so freshness is a layer on top, not
a slower probe. A stale row goes amber with the published version, an
owner-only Update button, and a note that live sessions keep their binary
until they restart.

POST /api/deps/update runs `npm install -g <pkg>@latest` — owner-gated,
audited, never via sudo, and it prefers the npm beside the resolved binary so
an nvm box can't install into a different prefix than the one it runs from.

Also fixes a latent bug in the same file: deps.ts resolved binaries with a
bare `command -v` while claude-cli.ts walks $CLAUDE_BIN → PATH →
~/.local/bin/claude, so a box whose unit ships a minimal PATH reported the
runtime MISSING while sessions launched fine. Both now share
claudeBinCandidates() and honour the same order — resolving PATH first would
report the version of a binary sessions never run.

scripts/deps-freshness-test.cjs (38 assertions) wired into test:governance.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant