Skip to content

fix(cli): restore Anton CLI for MindsHub users — minds-cloud crash + self-update loop (ENG-655)#240

Merged
alecantu7 merged 3 commits into
stagingfrom
alejandrocantu/eng-655-anton-cli-minds-cloud-and-updater
Jul 8, 2026
Merged

fix(cli): restore Anton CLI for MindsHub users — minds-cloud crash + self-update loop (ENG-655)#240
alecantu7 merged 3 commits into
stagingfrom
alejandrocantu/eng-655-anton-cli-minds-cloud-and-updater

Conversation

@alecantu7

Copy link
Copy Markdown
Contributor

Summary

Two verified failures that made the standalone anton CLI unusable for MindsHub users. Desktop app + webapp are unaffected — they build the LLM client via cowork-server's own build_llm_client and never invoke this updater (verified during the blast-radius review).

Ticket: ENG-655

1 · Crash: ValueError: Unknown planning provider: minds-cloud

The CLI's LLMClient registry only knows anthropic/openai/openai-compatible. MindsHub speaks the OpenAI-compatible API, but the desktop/consolidated config uses the first-class name minds-cloud, which the CLI never mapped — so a shared config hard-crashes LLMClient.from_settings.

Fix: an AntonSettings field-validator normalizes minds-cloud (and the minds_cloud spelling) → openai-compatible, so the existing model_post_init derives creds/base from the minds_* fields and the existing provider handles it.

Reproduced locally before/after:

before: ValueError: Unknown planning provider: minds-cloud
after:  planning_provider -> 'openai-compatible'; LLMClient built OK

2 · Perpetual failed self-update that corrupts installs (Windows)

__version__ was hardcoded in anton/__init__.py and drifted from the release tags (stuck at 2.26.6.30.1 across ~3 releases) because the release workflow only tags (pyproject is hatch-vcs / version-from-tag). The updater read that stale constant as local_ver, so remote(tag) > local was always true → it force-reinstalled the running tool on every launch → on Windows the partial reinstall bricked the env (No module named 'rich._emoji_codes', cannot import name 'rich_utils' from 'typer', or No module named 'anton').

Fix:

  • Derive __version__ from installed package metadata (importlib.metadata) so it always matches what's installed — kills the drift class at the root (no manual bump to forget).
  • Anti-thrash guard in updater.py: record a tag whose reinstall didn't take and don't force-reinstall that same tag again until a genuinely newer one appears.

3 · Escape hatch + dep cap

  • Add a discoverable --no-update flag (surfacing the existing but hidden ANTON_DISABLE_AUTOUPDATES setting — users had no way to find it).
  • Cap rich<15. (rich 14 + typer 0.24 are verified compatible; the reported rich/typer errors were reinstall corruption, not a version range.)

Tests

  • 5 for minds-cloud normalization, incl. the exact from_settings crash repro.
  • 3 for the updater anti-thrash marker.
  • Full suite passes. The 2 test_chat_scratchpad failures are pre-existing/environmental — they fail on clean staging too (the scratchpad subprocess can't spawn in the sandbox).

⚠️ Reviewer note

The updater changes (dynamic version + anti-thrash) resolve the loop deterministically in unit tests, but the uv tool install / hatch-vcs interaction should be sanity-checked with a real uv tool install git+…@<tag> on macOS + Windows before we lean on it in the wild.

Immediate user workaround (until this ships)

Set ANTON_DISABLE_AUTOUPDATES=true in ~/.anton/.env.

Not included (tracked separately)

  • ENG-656 — harden cowork-server's boot .env→DB migration (skip-and-log invalid provider). Different repo; latent, not triggered by these bugs.
  • Provisioned Lightsail agent instances also run anton — worth confirming they don't boot via this updater path (or that the snapshot sets ANTON_DISABLE_AUTOUPDATES).

🤖 Generated with Claude Code

alecantu7 and others added 2 commits July 7, 2026 17:28
…self-update loop (ENG-655)

Two verified failures that made the standalone `anton` CLI unusable for MindsHub
users (desktop app + webapp are unaffected — they build the LLM client via
cowork-server's own build_llm_client and never call this updater).

1. Crash: `ValueError: Unknown planning provider: minds-cloud`.
   The CLI's LLMClient registry only has anthropic/openai/openai-compatible;
   MindsHub speaks the OpenAI-compatible API but the desktop/consolidated config
   uses the first-class name "minds-cloud", which the CLI never mapped. Add an
   AntonSettings field-validator that normalizes minds-cloud (and the underscore
   spelling) → openai-compatible, so model_post_init derives the creds/base from
   the minds_* fields and the existing provider handles it. Reproduced the crash
   and the fix in a test.

2. Perpetual failed self-update that corrupts installs (reported on Windows).
   `__version__` was hardcoded in anton/__init__.py and drifted from the release
   tags (stuck at 2.26.6.30.1 across several releases) because the release
   workflow only tags (pyproject is hatch-vcs / version-from-tag). The updater
   read that stale constant as local_ver, so remote(tag) > local was always true
   → it force-reinstalled the running tool on every launch → on Windows that
   partial reinstall bricked the env (missing rich._emoji_codes, typer rich_utils
   ImportError, or "No module named 'anton'").
   - Derive __version__ from installed package metadata (importlib.metadata) so it
     always matches what's installed — kills the drift class at the root.
   - Anti-thrash guard in updater.py: record a tag whose reinstall didn't take and
     don't force-reinstall that same tag again until a newer one appears.

3. Escape hatch + dep cap: add a discoverable `--no-update` CLI flag (surfacing
   the existing ANTON_DISABLE_AUTOUPDATES setting), and cap `rich<15` (rich 14 +
   typer 0.24 verified compatible; the reported errors were reinstall corruption,
   not a version range).

Tests: 5 for minds-cloud normalization incl. the from_settings crash repro; 3 for
the updater anti-thrash marker. Full suite passes (the 2 scratchpad failures are
pre-existing/environmental — they fail on clean staging too; subprocess can't
spawn in the sandbox).

NOTE for reviewer: the updater changes (dynamic version + anti-thrash) resolve the
loop deterministically in unit tests, but the uv-tool-install / hatch-vcs
interaction should be sanity-checked with a real `uv tool install git+...@<tag>`
on macOS + Windows before relying on it in the wild.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… legacy anton tool (ENG-655)

Local uv-flow testing surfaced an additional root cause of the update loop.
`_read_installed_anton_version()` matched `re.search(r"anton\s+(\S+)")`, but a
real machine can have BOTH a leftover legacy `anton` tool (pre-rename) and the
current `anton-agent` tool — `uv tool list` shows `anton` first, so the bare
regex read the legacy tool's version (2.26.5.13.1) instead of anton-agent's
(2.26.7.6.2). That made installed_ver != remote_ver → "Update skipped" forever,
even after a correct install, and combined with the new anti-thrash marker would
permanently block updates.

Anchor the match to `anton-agent` (line-start, optional v prefix). Verified
against the real `uv tool list` output. Two tests: dual-tool ordering, and the
absent-anton-agent case (returns None → caller bails safely).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@alecantu7

Copy link
Copy Markdown
Contributor Author

Local uv-flow verification (the reviewer note above)

Tested the uv install path in throwaway /tmp venvs (never touching a live uv tool), which validated the version fix and surfaced one more root cause:

✅ hatch-vcs derives the correct version from a git-tag install. Installing anton-agent @ git+…@v2.26.7.6.2 gives importlib.metadata.version("anton-agent") == 2.26.7.6.2, while the tag's hardcoded __init__.py still says 2.26.6.30.1 — exactly the drift. Installing this branch: anton.__version__ == metadata (MATCH). So the dynamic-version fix produces the right local_ver, which resolves the loop.

➕ Additional root cause found + fixed (commit d287e2a): the test machine had both a leftover legacy anton uv tool (v2.26.5.13.1, from before the anton → anton-agent rename) and the current anton-agent (v2.26.7.6.2), plus cowork-server. uv tool list lists anton first, so _read_installed_anton_version()'s re.search(r"anton\s+(\S+)") read the legacy tool's version → installed_ver != remote_ver → "Update skipped" forever, even after a correct install. Anchored the regex to anton-agent; verified against the real uv tool list output (now parses 2.26.7.6.2 instead of v2.26.5.13.1).

Still not exercised: the literal uv tool install --force mutation of a live tool (I won't run that against a real install). The version-derivation + verification logic it depends on is now validated in venvs + unit tests.

Tests: 28 pass (added dual-tool parsing cases).

…alidator (ENG-655)

Adversarial review of PR #240 surfaced two real gaps:

1. __version__ resolved only the "anton-agent" distribution → on a legacy install
   under the old "anton" dist name it raised PackageNotFoundError and fell back
   to "0.0.0.dev0", which makes the updater see local < remote and force-reinstall
   once per release — reintroducing the exact ENG-655 loop for that cohort. Now
   try "anton-agent" then "anton" before the dev fallback. Also wrap the whole
   block defensively: __init__ runs on every `import anton` (incl. the desktop's
   cowork-server), so it must never raise.

2. The minds-cloud → openai-compatible validator was case-sensitive; "MINDS-CLOUD"
   or padded values slipped through and re-hit the crash. Now lower()/strip()
   tolerant.

Cross-surface review confirmed no app/webapp/instance regressions (validate_assignment
is off so the harness setattr path is unaffected; cowork-server reads
importlib.metadata directly, not anton.__version__; rich 14.3.3 satisfies <15;
updater is CLI-only).

Tests: added case/whitespace normalization case; suite 1148 passed (2 pre-existing
scratchpad-subprocess failures unrelated, fail on clean staging too).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@alecantu7

Copy link
Copy Markdown
Contributor Author

Adversarial review (3 independent reviewers: import/version, updater, cross-surface) + fixes

Ran a strict multi-lens review of this PR. Two real gaps found and fixed in 6393aa7, one pre-existing architectural issue split out as a follow-up, and the cross-surface safety claim independently confirmed.

Fixed in this PR (6393aa7)

  1. Legacy-dist __version__ (MEDIUM). version("anton-agent") raised PackageNotFoundError on installs under the legacy anton dist name → __version__ fell back to 0.0.0.dev0 → reintroduced the "always update" loop for that cohort. Now tries anton-agentanton → dev fallback, and the whole block is defensively wrapped (import anton must never raise — the desktop imports it too).
  2. Case-sensitive validator (LOW). minds-cloud normalization now tolerates case/whitespace (MINDS-CLOUD, " Minds_Cloud ").

Split out — ENG-661 (pre-existing, not a regression here)

Updater timeout race (HIGH-ish): the thread-join and the uv tool install --force subprocess share the same _TOTAL_TIMEOUT = 10s, and 10s is too short for a git install — so on slow/Windows machines the join can abandon a still-running --force reinstall mid-write (corruption), and the anti-thrash marker isn't written on the timeout path. This PR reduces the frequency (updates now fire only on a genuine new release, not every launch) but doesn't redesign the threading/timeout model. Tracked in ENG-661 (needs detached/atomic-install design + real macOS/Windows testing) rather than expanding this PR's scope.

Confirmed safe (cross-surface)

No app/webapp/instance regression: validate_assignment is off so the harness's post-construction setattr is unaffected; cowork-server reads importlib.metadata.version directly (not anton.__version__); rich 14.3.3 satisfies <15; the updater is reachable only from anton.cli (never from cowork-server or the containers).

Tests: 1148 passed (+ new case/whitespace + dual-dist coverage); the 2 test_chat_scratchpad failures are pre-existing/environmental (fail on clean staging — sandbox can't spawn the subprocess).

@alecantu7
alecantu7 requested a review from torrmal July 8, 2026 01:20

@torrmal torrmal left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good and from what I can see it wont break cowork-server
@sejubar can you test this

@alecantu7
alecantu7 merged commit b91a632 into staging Jul 8, 2026
5 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 8, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants