Repository navigation
Update nudges, and five bug fixes from a full-codebase review - #7
Merged
Merged
Conversation
readFileOrEmpty returned nil on any read error, not just a missing file, so a target .claude.json or settings.json that exists but momentarily can't be read (AV scan, ACL mishap) was treated as empty — and the merge then rewrote the file with only the merged keys, destroying the account's identity block or hand-edited settings. The renamed readFileIfPresent treats only ErrNotExist as empty and surfaces every other error, which now aborts the item instead of writing.
Three profile-store fixes: - A state.json that fails to parse was silently discarded, and the next TouchProfile overwrote it — permanently losing every directory link. It is now moved aside to state.json.corrupt with a warning, and saves go through write-then-rename so a crash mid-save can't truncate the file in the first place. - Create and Rename checked for collisions with exact-case os.Stat while Find resolves case-insensitively, so Linux could hold both 'work' and 'Work' — ambiguous everywhere a name is looked up — and Windows wrongly rejected a case-only rename. Both now collide the way Find resolves, and case-only renames of the same profile are allowed. - Rename and Delete keyed last-used state by the caller's spelling of the name rather than the resolved profile name, dropping recency on a case-insensitive rename and leaving stale entries behind.
Routing the npm claude.cmd through exec.Command("cmd.exe", "/c", bin,
args...) let Go quote each argument CommandLineToArgvW-style, but
cmd.exe parses by its own rules: with a quoted path plus one quoted
argument it strips the first and last quote on the line and tries to
run the path's first word. Reproduced with a batch file under a
spaced path — 'ccswitch run work -- -p "two words"' failed to launch.
The /s /c "..." idiom hands cmd.exe the whole payload in one outer
pair of quotes it strips verbatim; verified against spaced paths,
no args, and quoted args. The batch routing now lives in one
claudeCommand helper shared by every claude invocation.
A refresh while a previous batch's replies were still in flight let
the stale replies decrement the new batch's pending counter, so the
footer said 'updated' while rows still showed 'checking'. Fetches now
carry a generation stamp; stale replies still fill rows in but no
longer count the new batch down.
The --every guard also said 'capped at 15s minimum' while actually
rejecting — it now says what it does ('must be at least 15s'), and a
bare '--every=' errors like a missing value instead of being silently
ignored.
Neither tool announced updates: ccswitch upgrade is manual, and the claude binary's own updater can lag or be disabled. A detached helper (the hidden update-check command) now refreshes a small cache at most once a day — latest ccswitch release, latest Claude Code on the npm registry, and the installed claude --version — and the moments you're back at a prompt print one line per stale tool with the command that updates it. No launch ever waits on the network: nudges read only the cache. CCSWITCH_NO_UPDATE_CHECK=1 silences the whole feature. cmdRun also now ignores Ctrl-C while claude runs: the terminal delivers the interrupt to the whole foreground group, and claude traps it to cancel a response — its wrapper dying instead dropped a shell prompt onto a live TUI and lost the exit-code passthrough.
install.sh is piped straight to sh from raw.githubusercontent.com — a CRLF commit would break it with '\r: command not found'. The index is already all-LF; .gitattributes now keeps it that way, with PowerShell scripts as the one CRLF exception.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Feature: update nudges
Neither ccswitch nor the
claudebinary it launches announced new versions. A detached helper now refreshes a small cache at most once a day — latest ccswitch release, latest Claude Code on the npm registry, and the installedclaude --version— and the moments you're back at a prompt (Claude Code exiting, the picker closing,ccswitch version) print one line per stale tool with the command that updates it:No launch ever waits on the network (nudges read only the cache, the refresh is a separate process), nothing installs itself, and
CCSWITCH_NO_UPDATE_CHECK=1turns it all off. Verified end-to-end against the real endpoints.Bug fixes (from a two-agent review of the whole codebase, each finding verified against the code before fixing)
.claude.json/settings.jsonwas treated as "file is empty", so the merge rewrote the file with only the merged keys — destroying the identity block the code explicitly promises to preserve. Read errors now abort the write.state.jsonwas silently wiped. A truncated state file parsed to empty and the nextrun/usesaved that emptiness, losing every directory link. It's now moved aside tostate.json.corruptwith a warning, and saves are write-then-rename so truncation can't happen mid-save.cmdRunnow ignores it for the duration of the child.claude.cmdfailed to launch from a spaced path with quoted args ('C:\Users\Ryan' is not recognized…). Reproduced empirically; fixed with thecmd /s /c "..."idiom and verified against spaced paths, no args, and quoted args.workandWork(ambiguous everywhere, since lookup is case-insensitive), Windows wrongly rejected case-only renames, and rename/delete keyed last-used state by the caller's spelling. All aligned with howFindresolves.--every's error now says what it means.New regression tests cover the store fixes and the version-compare/nudge logic.
go vet,go test, and builds for windows/linux/darwin all pass.Known limitation (not fixed here): concurrent ccswitch invocations can race on
state.json(load-modify-save with no lock); the window is milliseconds and the fix needs file locking, so it's left documented.Also adds
.gitattributesenforcing LF (CRLF ininstall.shwould breakcurl | sh).