4845 Commits
Author SHA1 Message Date
semantic-release-bot f45fa923f2 chore(release): 8.10.0 [skip ci]
## [8.10.0](https://github.com/kaitranntt/ccs/compare/v8.9.0...v8.10.0) (2026-09-11)

### Features

* **api:** add OrcaRouter provider preset ([ea7906b](https://github.com/kaitranntt/ccs/commit/ea7906b97580533c158e09ff8777f7c66d248ac6))
* **cliproxy:** add Grok 4.6 selection ([c30e9ca](https://github.com/kaitranntt/ccs/commit/c30e9ca6179d584174622dce971b05e664ad98f7))
* **cliproxy:** add meetsBackendMinimumVersion comparator helper ([3658c01](https://github.com/kaitranntt/ccs/commit/3658c0113729514dc660af99ade379644c57186d))
* **cliproxy:** clarify quota percentages are remaining in CLI output ([eca266a](https://github.com/kaitranntt/ccs/commit/eca266ad30851040f0605ae03ec6d5571032e90b)), closes [#1714](https://github.com/kaitranntt/ccs/issues/1714)
* **dashboard:** collapse the CLIProxy status widget ([fde7bd8](https://github.com/kaitranntt/ccs/commit/fde7bd83db96ce4fa87cb3b5403a7ab51c4ccde9))
* **models:** add Claude Fable 5.1 and correct Sonnet 5 pricing ([3583b54](https://github.com/kaitranntt/ccs/commit/3583b544d01170d0352a49f3e4bfc8b5ecd2c953))

### Bug Fixes

* **ci:** publish dev releases with public access for scoped package ([#1715](https://github.com/kaitranntt/ccs/issues/1715)) ([ae1559a](https://github.com/kaitranntt/ccs/commit/ae1559aeb01d466787026bdabff0c24d58895bd0))
* **cliproxy:** correct ARM release asset architecture boundaries ([#1725](https://github.com/kaitranntt/ccs/issues/1725)) ([87f1d9b](https://github.com/kaitranntt/ccs/commit/87f1d9b4e874f63a61d337357796db8e27a4f18f))
* **cliproxy:** correct pool routing version capability map ([#1726](https://github.com/kaitranntt/ccs/issues/1726)) ([b9601cb](https://github.com/kaitranntt/ccs/commit/b9601cb913dce355e2a5f3347500efe024e79974))
* **cliproxy:** fail-closed on unsupported drain-order priority selection ([#1724](https://github.com/kaitranntt/ccs/issues/1724)) ([ed1badc](https://github.com/kaitranntt/ccs/commit/ed1badcfcb90f6e096ec06f28c27fecd270c0239))
* **cliproxy:** finish concurrent update recovery ([fc56eca](https://github.com/kaitranntt/ccs/commit/fc56ecaac4ed8218f5d0408a6c78f173a11a78da))
* **cliproxy:** harden update recovery paths ([4502e5d](https://github.com/kaitranntt/ccs/commit/4502e5d503b4ae73dda6187d9044263364019565))
* **cliproxy:** keep proxy available during updates ([8e4def4](https://github.com/kaitranntt/ccs/commit/8e4def4713a9932c7b741e9bf791ee9904d4d695))
* **cliproxy:** make formatQuotaBar pure ASCII and add doctor quota tests ([745cb74](https://github.com/kaitranntt/ccs/commit/745cb743181362372fd5f89d20e67ea10cc4ed89))
* **delegation:** flush stdout before exit to prevent piped output truncation ([3e49834](https://github.com/kaitranntt/ccs/commit/3e49834c9daa0a965316a1c26bbb9df7e0b09908))
* **docker:** target live cliproxy compose stack ([54c3e86](https://github.com/kaitranntt/ccs/commit/54c3e86e894fe6563a4e944709b1dae65bbf360b))
* **docs:** format star history chart with valid sealed_token url ([03ff7d8](https://github.com/kaitranntt/ccs/commit/03ff7d840001d504642c2fa25dc0c29d661be2fd))
* **docs:** format star history chart with valid sealed_token url ([0598d2a](https://github.com/kaitranntt/ccs/commit/0598d2a7ba3fd2d8d0e09b825f9000d66c2e6f08))
* **docs:** update star history chart with encrypted sealed_token ([eadcef2](https://github.com/kaitranntt/ccs/commit/eadcef22897859ae89596d675296fa974df91e03))
* **docs:** update star history chart with encrypted sealed_token ([583d0cf](https://github.com/kaitranntt/ccs/commit/583d0cf8fca901885beeb10c5e9b379430e5a070))
* **extended-context:** manage ANTHROPIC_DEFAULT_MODEL and keep the overlay in sync ([7ae7a5e](https://github.com/kaitranntt/ccs/commit/7ae7a5eba6c75871f6822567079d841d9cd73789))
* **extended-context:** never write [1m] into the Fable tier key ([2b780ce](https://github.com/kaitranntt/ccs/commit/2b780ceae3bcc5a4cbaf809601e6d50fef1ae15b))
* **extended-context:** suffix the Fable tier and default it on 1M launches ([02c45e0](https://github.com/kaitranntt/ccs/commit/02c45e088a841fe9a8d6192743f18a8f99cd2724))
* **image-analysis:** honor configured profile_backends at launch ([fe3447f](https://github.com/kaitranntt/ccs/commit/fe3447f4871c4b53c2bf0f2f9e9cba9d057002bf))
* **image-analysis:** route original backend at CLIProxy root ([afa663b](https://github.com/kaitranntt/ccs/commit/afa663b5b2f999b19bef14a208044e02e8b3c122)), closes [#1703](https://github.com/kaitranntt/ccs/issues/1703)
* **openrouter:** use /api endpoint root to prevent duplicated /v1 ([#1728](https://github.com/kaitranntt/ccs/issues/1728)) ([375a346](https://github.com/kaitranntt/ccs/commit/375a3468b8c5de1ad9af963db5abb285feaffc94))
* **README:** update star history chart to use working domain ([6e3a16b](https://github.com/kaitranntt/ccs/commit/6e3a16b84c7eb50357da426f350b7711cb4694cd))
* **shared-manager:** publish adopted settings by replacement ([00a4dce](https://github.com/kaitranntt/ccs/commit/00a4dceb94a5a21d740e7e7d057c6a1b7398d8ee))
* **update:** detect pnpm v9+ global store layouts ([c43cd6c](https://github.com/kaitranntt/ccs/commit/c43cd6caf5002514d8c9d7e3ff7da2a2ce61253c)), closes [#1706](https://github.com/kaitranntt/ccs/issues/1706)
* **websearch:** filter disabled legacy CLI probes and skip unused version fetching ([f491169](https://github.com/kaitranntt/ccs/commit/f4911694e37aaa97dbdfefac0b0f712c4e81e435)), closes [#1716](https://github.com/kaitranntt/ccs/issues/1716)
* **websearch:** format status.ts ([c0c3699](https://github.com/kaitranntt/ccs/commit/c0c36991d650cd4129ea1a910f0a3ad012373064))

### Documentation

* **docker:** document host continuity ([ee891bd](https://github.com/kaitranntt/ccs/commit/ee891bdd0edcdda0faa658cf6d28acd352b5c3c1))
* refresh hardening inventory after [#1727](https://github.com/kaitranntt/ccs/issues/1727) ([247c033](https://github.com/kaitranntt/ccs/commit/247c0336c7f91cb143531eeff97da748d4457092))
* refresh hardening inventory after dev merge ([8cb3451](https://github.com/kaitranntt/ccs/commit/8cb3451f8153af3be6b855f3651d925efeb4e1de))
* refresh hardening inventory after merge ([a4281ce](https://github.com/kaitranntt/ccs/commit/a4281cea1b0bc090123087d154b294e9d69124cd))
* refresh hardening inventory and add 75% quota test ([eddc404](https://github.com/kaitranntt/ccs/commit/eddc404f2499b73acd96a8ec3b6d52c52916e05a))
* refresh hardening inventory counts ([b9190a6](https://github.com/kaitranntt/ccs/commit/b9190a6e57708f576a5c322f3d12e470eb9d6b36))
* **shared-manager:** note why the CAS mismatch raises EEXIST ([4a17f03](https://github.com/kaitranntt/ccs/commit/4a17f034e24f1bcbed3067166e184e933cf17746))

### Code Refactoring

* **shared-manager:** extract the durable temp write ([df52662](https://github.com/kaitranntt/ccs/commit/df52662a59517c495ea21056055c5d65ba1ba813))
* **shared-manager:** tighten canonical identity capture ([fa3fd8e](https://github.com/kaitranntt/ccs/commit/fa3fd8eb3a7c76781defe61771d9f8f3418f2a3e))

### Tests

* **cliproxy:** strengthen fallback download url and ungated show/reset coverage ([fcbac62](https://github.com/kaitranntt/ccs/commit/fcbac629d277c9fc65f81817ca70108fdc88c062))
* **openrouter:** refine regression test using existing profile-writer harness ([#1728](https://github.com/kaitranntt/ccs/issues/1728)) ([283e884](https://github.com/kaitranntt/ccs/commit/283e8843ac624377bc6422a2e704724baf8a5e07))

### CI

* **docker:** allow cold-start healthcheck window ([78ad297](https://github.com/kaitranntt/ccs/commit/78ad297e30f0e80906af1a92db73d43f47cf9a91))
* merge main Docker smoke fix into dev ([8ae7dd8](https://github.com/kaitranntt/ccs/commit/8ae7dd88f3c9fefa1e31173ef46374e62282a491))
2026-09-11 14:10:46 +00:00
Kai (Tam Nhu) Tran 6f88efb881 Merge pull request #1732 from kaitranntt/dev
feat: promote dev to main
2026-09-11 08:06:47 -06:00
github-actions[bot] 46daba0c6b chore(release): 8.9.0-dev.11 2026-09-11 13:59:45 +00:00
Kai (Tam Nhu) Tran d9cf5c0796 Merge pull request #1731 from kaitranntt/kai/fix-1728-openrouter-base-url
fix(openrouter): use /api endpoint root to prevent duplicated /v1 (#1728)
2026-09-09 16:00:56 -06:00
Kai (Tam Nhu) Tran 54c6695508 Merge pull request #1730 from kaitranntt/kai/fix-cliproxy-backend-version-gates
fix(cliproxy): backend version compatibility gates and ARM assets (#1724, #1725, #1726)
2026-09-09 16:00:33 -06:00
github-actions[bot] 478e3f3702 chore(release): 8.9.0-dev.10 2026-09-09 21:53:39 +00:00
Tam Nhu Tran 283e8843ac test(openrouter): refine regression test using existing profile-writer harness (#1728) 2026-09-09 15:41:42 -06:00
Tam Nhu Tran 375a3468b8 fix(openrouter): use /api endpoint root to prevent duplicated /v1 (#1728) 2026-09-09 15:38:22 -06:00
Tam Nhu Tran aa33904473 Merge remote-tracking branch 'origin/dev' into kai/fix-cliproxy-backend-version-gates 2026-09-09 15:24:25 -06:00
Tam Nhu Tran 8cb3451f81 docs: refresh hardening inventory after dev merge 2026-09-09 15:24:08 -06:00
Kai (Tam Nhu) Tran cb9eea1182 Merge pull request #1729 from kaitranntt/kai/docs/hardening-inventory-refresh-post-1727
docs: refresh hardening inventory after #1727
2026-09-09 15:23:28 -06:00
Tam Nhu Tran 247c0336c7 docs: refresh hardening inventory after #1727 2026-09-09 15:23:19 -06:00
Tam Nhu Tran 83c76d826d Merge remote-tracking branch 'origin/dev' into kai/fix-cliproxy-backend-version-gates 2026-09-09 15:14:58 -06:00
Kai (Tam Nhu) Tran 24582766fe Merge pull request #1727 from KennethWKZ/feat/claude-fable-5-1
feat(models): add Claude Fable 5.1 and correct Sonnet 5 pricing
2026-09-09 15:14:36 -06:00
Kenneth Wong 7ae7a5eba6 fix(extended-context): manage ANTHROPIC_DEFAULT_MODEL and keep the overlay in sync
Claude Code reads ANTHROPIC_DEFAULT_MODEL as its lowest-priority startup
model (after --model, ANTHROPIC_MODEL and the settings `model` field). It is
a plain model id, so behind a proxy a bare value is clamped to 200k exactly
like the tier keys. Add it to EXTRA_EXTENDED_CONTEXT_MODEL_ENV_KEYS so
--1m/--no-1m and the saved preference toggle it with the rest.

The `--settings` launch overlay only carried the routing keys plus the five
Anthropic tier keys. Claude Code applies the settings `env` block on top of
the process environment, so a bare CLAUDE_CODE_SUBAGENT_MODEL or
ANTHROPIC_DEFAULT_MODEL persisted on disk silently undid the resolved
--1m result (and a saved [1m] undid --no-1m). Overlay the extended-context
extra keys too.

Verified with a stub Claude binary reading the overlay file:

  ccs claude --no-1m -> ANTHROPIC_DEFAULT_MODEL=claude-opus-5, CLAUDE_CODE_SUBAGENT_MODEL=claude-sonnet-5
  ccs claude --1m    -> both keys suffixed with [1m]
2026-09-04 08:34:33 +08:00
Kenneth Wong 02c45e088a fix(extended-context): suffix the Fable tier and default it on 1M launches
The previous commit assumed Claude Code's fable resolver strips [1m] and
therefore never wrote the suffix into ANTHROPIC_DEFAULT_FABLE_MODEL. That
premise only holds when ANTHROPIC_BASE_URL is unset or points at
api.anthropic.com. Behind a proxy (CLIProxy, headroom, ...) Claude Code
2.1.259 does the opposite:

- the fable alias resolver passes the env value through untouched, so a
  saved `claude-fable-5-1[1m]` reaches the model picker as-is;
- the context-window resolver grants 1M unconditionally when the model id
  carries [1m], and otherwise trusts a natively-1M model only when the base
  URL is first-party. Bare `claude-fable-5-1` via 127.0.0.1 is clamped to
  200k even though CLIProxy's /v1/models advertises max_input_tokens 1M.

So the suffix is the only thing that turns the long window on for CCS, and
stripping it from the Fable key is what kept `ccs claude --model fable` at
200k. A headroom settings profile with the suffix saved on that key showed
the 1M window in the same Claude Code build, which is how the inversion was
caught.

Changes:

- Drop the suffix-stripping key guard; ANTHROPIC_DEFAULT_FABLE_MODEL now
  receives and keeps [1m] exactly like the opus/sonnet tiers.
- On a long-context launch (explicit --1m, or a saved [1m] on any Anthropic
  tier key) fill a missing Fable tier with the catalog Fable model plus
  [1m]. The model-neutral claude profile pins no Fable model, so `--model
  fable` used to fall back to Claude Code's bare default and lose the window
  even when every other tier asked for 1M. An explicit mapping always wins,
  --no-1m never fills, and providers without a catalog Fable model are left
  alone.
- Add getDefaultFableTierModel() to the model catalog for that default.

Verified with a stub Claude binary that dumps its environment:

  ccs claude --model fable          -> ANTHROPIC_DEFAULT_FABLE_MODEL=claude-fable-5-1[1m]
  ccs claude --1m                   -> ANTHROPIC_DEFAULT_FABLE_MODEL=claude-fable-5-1[1m]
  ccs claude --no-1m --model fable  -> no Fable tier written, ANTHROPIC_MODEL stripped
2026-09-04 02:48:15 +08:00
Kenneth Wong 2b780ceae3 fix(extended-context): never write [1m] into the Fable tier key
Claude Code resolves ANTHROPIC_DEFAULT_FABLE_MODEL through a resolver that
strips the [1m] suffix before use, unlike the opus/sonnet resolvers which
pass their env value through untouched. The stripped value is then treated
as an env-supplied default and held to the standard 200k window instead of
the model's native 1M, so writing [1m] into that key costs the long context
window rather than granting it.

Measured against a Claude Code 2.1.259 session (`/context` on a live proxy
endpoint):

  ANTHROPIC_MODEL=claude-fable-5-1[1m]               -> 1m window
  ANTHROPIC_MODEL=claude-fable-5-1                   -> 200k window
  ANTHROPIC_DEFAULT_FABLE_MODEL=claude-fable-5-1[1m] -> 200k window

The fix is key-scoped rather than model-scoped, since the suffix is what
grants 1M on every other key. Adds envKeyAcceptsExtendedContextSuffix() as
the single place recording which keys reject the suffix, enforces it as a
floor inside applyExtendedContextPreferenceToAnthropicModels() so no caller
can bypass it, and strips a previously saved suffix from that key on the
next launch through the auto path.

Also extends the preference to CLAUDE_CODE_SUBAGENT_MODEL. Subagent windows
come from the same resolver as the main loop (the Explore inheritCap bounds
the model tier, not the context window), so a bare subagent model id sits at
200k while the suffixed form gets 1M. The new key list is kept separate from
ANTHROPIC_MODEL_ENV_KEYS, which also drives routing, model-id normalization
and profile validation.

Claude-Session: https://claude.ai/code/session_01NPkafQjVf4pSwPBwBistGk
2026-09-03 17:08:23 +08:00
Kenneth Wong 3583b544d0 feat(models): add Claude Fable 5.1 and correct Sonnet 5 pricing
Register `claude-fable-5-1` in the CLIProxy model catalog, the dashboard
catalog, and the usage pricing registry.

Pricing is taken from Anthropic's official pricing page:

- Fable 5.1 base rates are $10/$50 per MTok with a $12.50 5m cache write,
  matching Fable 5.
- Cache hits bill at 0.025x base input ($0.25/MTok) rather than the
  standard 0.1x multiplier. Anthropic applies that reduced rate only to
  Fable 5.1 and Mythos 5.1, so this entry cannot derive its cache rates
  from CACHE_READ_MULTIPLIER.

This also corrects Claude Sonnet 5 from $3/$15 to $2/$10 (cache write
$2.50, cache read $0.20). The launch introductory rate became the
standard price and the increase scheduled for 2026-09-01 was cancelled,
so the previous entry over-reported Sonnet 5 usage cost by 50%.

Thinking on Fable 5.1 is always on and can only be steered through
effort levels, so the catalog entry exposes the same `low`..`max` level
surface as Fable 5 and Opus 5 instead of a manual token budget.

The GitHub Copilot catalog is deliberately left unchanged, since Copilot
availability for Fable 5.1 is not verified; surfacing it there would
offer a model the backend may reject.

Regenerates docs/reports/hardening-inventory.{json,md} so the ci-parity
gate matches the source tree.
2026-09-03 16:26:41 +08:00
Tam Nhu Tran fcbac629d2 test(cliproxy): strengthen fallback download url and ungated show/reset coverage 2026-09-02 11:35:00 -04:00
Tam Nhu Tran 3ce7c01d95 chore(cliproxy): refresh formatting, throw-error baseline, and hardening inventory 2026-09-02 11:31:43 -04:00
Tam Nhu Tran ed1badcfcb fix(cliproxy): fail-closed on unsupported drain-order priority selection (#1724) 2026-09-02 11:14:57 -04:00
Tam Nhu Tran b9601cb913 fix(cliproxy): correct pool routing version capability map (#1726) 2026-09-02 11:14:57 -04:00
Tam Nhu Tran 87f1d9b4e8 fix(cliproxy): correct ARM release asset architecture boundaries (#1725) 2026-09-02 11:14:57 -04:00
Tam Nhu Tran 3658c01137 feat(cliproxy): add meetsBackendMinimumVersion comparator helper 2026-09-02 11:09:34 -04:00
github-actions[bot] 485c436958 chore(release): 8.9.0-dev.9 2026-08-27 03:05:30 +00:00
Tam Nhu Tran 03ff7d8400 fix(docs): format star history chart with valid sealed_token url 2026-08-26 23:01:20 -04:00
Tam Nhu Tran 0598d2a7ba fix(docs): format star history chart with valid sealed_token url 2026-08-26 23:01:17 -04:00
Tam Nhu Tran eadcef2289 fix(docs): update star history chart with encrypted sealed_token 2026-08-26 23:01:11 -04:00
Tam Nhu Tran 583d0cf8fc fix(docs): update star history chart with encrypted sealed_token 2026-08-26 23:01:07 -04:00
github-actions[bot] 768be0db8a chore(release): 8.9.0-dev.8 2026-08-25 20:27:08 +00:00
Kai (Tam Nhu) Tran 83ccb21f8d Merge pull request #1723 from kaitranntt/kai/docs/hardening-inventory-and-75-quota-test
docs: refresh hardening inventory and add 75% quota test
2026-08-25 16:22:52 -04:00
github-actions[bot] d10b5653ff chore(release): 8.9.0-dev.7 2026-08-25 20:14:40 +00:00
Tam Nhu Tran eddc404f24 docs: refresh hardening inventory and add 75% quota test 2026-08-25 16:13:41 -04:00
Kai (Tam Nhu) Tran 253439e001 Merge pull request #1722 from kaitranntt/kai/fix/ascii-quota-bar-and-doctor-tests
fix(cliproxy): make formatQuotaBar pure ASCII and add doctor quota tests
2026-08-25 16:10:19 -04:00
github-actions[bot] 51c603b0aa chore(release): 8.9.0-dev.6 2026-08-25 20:07:25 +00:00
Tam Nhu Tran 745cb74318 fix(cliproxy): make formatQuotaBar pure ASCII and add doctor quota tests 2026-08-25 16:04:33 -04:00
Kai (Tam Nhu) Tran 8f87937e8a Merge pull request #1721 from kaitranntt/kai/fix/1716-websearch-probe-perf
fix(websearch): filter disabled legacy CLI probes and skip unused version fetching (#1716)
2026-08-25 16:02:43 -04:00
Kai (Tam Nhu) Tran 21f10c007b Merge pull request #1720 from kaitranntt/kai/fix/1714-quota-remaining-label
feat(cliproxy): clarify quota percentages are remaining in CLI output (#1714)
2026-08-25 16:02:40 -04:00
Tam Nhu Tran c0c36991d6 fix(websearch): format status.ts 2026-08-25 15:56:12 -04:00
Tam Nhu Tran f4911694e3 fix(websearch): filter disabled legacy CLI probes and skip unused version fetching
Closes #1716
2026-08-25 15:35:54 -04:00
Tam Nhu Tran eca266ad30 feat(cliproxy): clarify quota percentages are remaining in CLI output
Closes #1714
2026-08-25 15:35:23 -04:00
github-actions[bot] 65dc902299 chore(release): 8.9.0-dev.5 2026-08-25 18:54:51 +00:00
Kai (Tam Nhu) Tran a5aa9ac35f Merge pull request #1717 from mardausdennis/feat/collapsible-proxy-status-widget
feat(dashboard): collapse the CLIProxy status widget
2026-08-25 14:50:28 -04:00
Kai (Tam Nhu) Tran ff27a36b92 Merge pull request #1718 from sgaluza/fix/adopt-settings-publish-by-replacement
fix(shared-manager): publish adopted settings by replacement
2026-08-25 14:50:19 -04:00
Sergey Galuza 4a17f034e2 docs(shared-manager): note why the CAS mismatch raises EEXIST
The code is this file's marker for "a race was detected" - the same one
the reappeared-path and concurrent-replacement guards raise - not a
report that a no-replace link() or open('wx') hit an existing path. Say
so at the throw, so debugging by err.code does not send anyone looking
for a no-replace failure that never happened.

Built [OnSteroids](https://onsteroids.ai)
2026-08-23 08:33:06 +02:00
Sergey Galuza df52662a59 refactor(shared-manager): extract the durable temp write
createFileNoReplace and publishCanonicalContent carried the same
open-wx / fchmod / write / fsync / close block and the same cleanup
handler. Extract writeDurableTempFile and discardTempFile so each
publisher is left with only what distinguishes it: a no-replace link, or
the compare-and-swap guard and the rename.

Also spell out in publishCanonicalContent that its guard is read-then-act
rather than atomic. POSIX has no compare-and-swap rename, so the window
is narrowed from the ~100 ms the old claim-and-republish path left open
to two adjacent syscalls, not closed - and the pre-image sidecar is what
keeps that last outcome recoverable. Worth stating so the guard is not
mistaken for a strict guarantee later.

Built [OnSteroids](https://onsteroids.ai)
2026-08-23 08:32:53 +02:00
Sergey Galuza fa3fd8eb3a refactor(shared-manager): tighten canonical identity capture
Build the canonical identity from the stat getCanonicalFile already
takes, instead of a second lstat of the same inode. One syscall less,
and mode, mtime and identity now describe the same moment rather than
two adjacent ones.

Assert in the adoption race tests that the foreign writer never fired.
It writes only when the canonical path is observed empty, so a zero
count states the invariant the fix establishes - the path is never left
without a regular file - instead of only checking the final content.

Built [OnSteroids](https://onsteroids.ai)
2026-08-23 08:32:39 +02:00
Sergey Galuza 00a4dceb94 fix(shared-manager): publish adopted settings by replacement
Adoption moved the canonical settings.json aside with rename() and left
the path empty until publication, roughly 100 ms later. Claude Code or a
second `ccs` starting inside that window found no file and seeded an
empty placeholder; publication then failed with EEXIST because link() is
no-replace, and the rollback published a backup and unlinked the claim,
destroying the only remaining copy of the user's settings. Recovering
meant digging through sidecar files by hand.

Publish by replacement instead: write a temp file next to the canonical
inode and rename() it over the target, so the path always holds a regular
file and no placeholder can be seeded. A compare-and-swap guard on
(ino, mtime, size) runs immediately before the rename and refuses to
publish when the canonical inode changed since it was read, so a writer
that got there first is still never clobbered. The pre-image backup is
published before the replacement, keeping the old content recoverable if
publication is interrupted.

Drops the canonical claim entirely along with restoreCanonicalClaim, and
folds the two identical sidecar publishers into one helper.
recoverOrphanedCanonicalClaim stays, since claims written by older
versions may still be on disk.

New tests cover both writers seen in the incident: Claude Code seeding
`{}` with a trailing newline, and a second `ccs` seeding the 2-byte
variant from shared-dir-linker. Four tests that pinned the claim-based
design were rewritten, among them `preserves a canonical write that
lands during no-replace publication`, whose intent is now enforced by
the CAS guard instead of by an EEXIST from a no-replace link.

Built [OnSteroids](https://onsteroids.ai)
2026-08-23 08:32:26 +02:00
mardausdennis fde7bd83db feat(dashboard): collapse the CLIProxy status widget
ProxyStatusWidget sits outside the sidebar ScrollArea, so every row it
renders is taken from the provider list. With the proxy running and pool
routing on it renders ten stacked blocks, and the only Collapsible covers
version management, which is already closed in that state.

Add a chevron that collapses the whole widget in local and remote mode,
keeping the status row, version row and action buttons visible. The
preference is stored in localStorage and defaults to expanded, so
existing setups are unchanged. Opening version settings from a collapsed
widget expands it again.
2026-08-22 01:15:41 +02:00
github-actions[bot] a53980782f chore(release): 8.9.0-dev.4 2026-08-21 15:33:50 +00:00