Repository navigation
v0.28.0
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_profiletakes an optional workspace.resolve_profile(name, workspace=...)resolves against a caller-suppliedWorkspaceinstead of the default one, so library consumers and their test suites can point profile resolution at an isolated root without settingCLAUDEWHEEL_CONFIG_DIR. Omitting it, or passingNone, keeps the existing behavior.
Fixes
importno 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.0safegit rewrite-authorspelling, which safegit turned into a stub that refuses to run -- so the guard fired on nothing while the livesafegit author rewritecommand, which rewrites author and committer across all history, reached subagents unguarded. Both the bare and./safegitinvocations now escalate. - Malformed settings no longer disable guardrail reconciliation. A
null(or otherwise non-object) value underprofileDefaults,claudewheelorpermissionsin a settings file raised an error that silently skipped every remaining profile at launch and crashedpatch-profileswith a traceback; such a value is now replaced with an empty object and reconciled. - Commands like
git stashedare no longer blocked. Thegit stashandgit restoreguardrail 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 realgit stashandgit restoresubcommands stay denied. - Reconcile now prunes two dead allow rules. The
sudo -n ufw statusallow entry was listed with unescaped parentheses while profiles store it with literal backslashes, so it survived every reconcile; andSkill(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.jsonorstate.jsonwas 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 mvno 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 diskclaudewheel mvnow 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 mvafter an interrupted move now explains that the rename already happened and names theclaudewheel mv --post-hoc OLD NEWcommand that finishes the session migration - A session migration that fails while
--resumeor--continueis repairing a renamed directory now printsError: ...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 withos.replace; secret files are never observable above 0600, and same-stem targets such asconfig.jsonandconfig.yamlno longer collide. - A malformed settings file is skipped, not silently rewritten.
patch-profiles/reconcile-permissionsused 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 asmalformed, 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-runno 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
hooksvalue in a profile's settings now produces a clear error naming the key and the type found, instead of crashing.