Skip to content

v0.28.0

Choose a tag to compare

@smm-h smm-h released this 07 Sep 11:04
· 681 commits to main since this release

Correctness sweep: atomic writes that survive concurrent processes, a guardrail model that guards the live safegit spelling, mv fixes for underscore paths and corrupt registries, loud errors for malformed settings values, and a workspace-injection seam on resolve_profile.

Context

An audit of every open todo against the real code, the live config tree, and
the installed Claude Code client falsified several long-held premises and
surfaced defects the todos did not know about. This release ships the
verified quick wins from that audit: the atomic-write race (two claudewheel
processes could publish a spliced, invalid settings or token file while both
reported success), the guardrail rule that guarded a retired safegit spelling
while the live history-rewriting command ran unguarded, session-store
encoding that disagreed with what Claude Code actually writes, silently
swallowed corrupt profile registries during mv, and silent normalization of
malformed settings values (now a loud per-target refusal naming the file and
key). Every fix carries a red-green regression test and passed a fresh-context
audit round before release.

Features

  • The guardrails page now documents every stripped tool with the reason it is banned, in a table generated from the canonical model
  • resolve_profile takes an optional workspace. resolve_profile(name, workspace=...) resolves against a caller-supplied Workspace instead of the default one, so library consumers and their test suites can point profile resolution at an isolated root without setting CLAUDEWHEEL_CONFIG_DIR. Omitting it, or passing None, keeps the existing behavior.

Fixes

  • import no longer aborts when the source store contains a broken symlink -- the dangling entry is named on stderr and skipped, and the remaining sessions import normally.
  • Guardrail now covers safegit author rewrite. The escalation guard on safegit history rewriting still matched the pre-0.19.0 safegit rewrite-author spelling, which safegit turned into a stub that refuses to run -- so the guard fired on nothing while the live safegit author rewrite command, which rewrites author and committer across all history, reached subagents unguarded. Both the bare and ./safegit invocations now escalate.
  • Malformed settings no longer disable guardrail reconciliation. A null (or otherwise non-object) value under profileDefaults, claudewheel or permissions in a settings file raised an error that silently skipped every remaining profile at launch and crashed patch-profiles with a traceback; such a value is now replaced with an empty object and reconciled.
  • Commands like git stashed are no longer blocked. The git stash and git restore guardrail matchers had no trailing word boundary, so any command whose subcommand merely started with those names -- git stashed, git stash-like, git restored -- was hard-denied. The real git stash and git restore subcommands stay denied.
  • Reconcile now prunes two dead allow rules. The sudo -n ufw status allow entry was listed with unescaped parentheses while profiles store it with literal backslashes, so it survived every reconcile; and Skill(gsd:discuss-phase) is an allow rule for a tool that is stripped from every launched session, so it can never fire. Both are now removed.
  • A corrupt config file no longer bleeds one launcher's state into another's files. When config.json, options.json or state.json was missing or unparseable, the store fell back to the built-in defaults object itself and then wrote into it -- schema versions, pinned values, launch counts and recent directories -- so a second launcher directory opened in the same process started from the first one's state and saved it to disk. The fallback is now a private copy.
  • claudewheel mv no longer silently skips session data for projects whose path contains an underscore -- the encoded project directory name now matches what Claude Code actually writes on disk
  • claudewheel mv now stops with a message naming the unreadable profile registry file, instead of blaming an unrelated project directory or reporting a successful migration that left that profile's project list pointing at the old path
  • Re-running claudewheel mv after an interrupted move now explains that the rename already happened and names the claudewheel mv --post-hoc OLD NEW command that finishes the session migration
  • A session migration that fails while --resume or --continue is repairing a renamed directory now prints Error: ... and stops, instead of ending the launch with a Python traceback
  • Concurrent writes no longer corrupt settings and token files. Two claudewheel processes writing the same file at once (two launches, a launch racing reconcile-permissions, a hook racing the TUI) shared one staging file, so a write could fail outright or publish a file spliced from both writers' bytes while both reported success. Every atomic write now stages through its own unique temp file and commits with os.replace; secret files are never observable above 0600, and same-stem targets such as config.json and config.yaml no longer collide.
  • A malformed settings file is skipped, not silently rewritten. patch-profiles / reconcile-permissions used to replace a value of the wrong type under a guardrail key ("profileDefaults": null, "claudewheel": [], a string "permissions") with an empty object and write the file back, destroying whatever was there. Such a file is now reported as malformed, naming its path and the offending key, and left byte-identical; every other profile is still reconciled.
  • Reconcile's "unreadable" skip message now names the settings file it could not read, matching the malformed-value message
  • --dry-run no longer silently accepts an invalid path operand in directory and file copies: it is refused the same way a real run refuses it.
  • A malformed hooks value in a profile's settings now produces a clear error naming the key and the type found, instead of crashing.