Mode was applied per session but stored in one file per machine
($CLAUDE_CONFIG_DIR/.caveman-active). Four bugs followed from that one
fact: parallel windows shared a mode; deactivation was spelled "no flag
file", so SessionStart re-derived the configured default and undid an
explicit "stop caveman" after every auto-compaction; the statusline badge
rendered whichever window wrote last; and "off" could not survive a
SessionStart at all.
payload's `source`, but it read the legacy flag, where off is absence — a
deactivated session found nothing stored and fell back to getDefaultMode()
regardless. The other three are consequences of the storage shape, not of
when the hook re-derives.
State now lives in .caveman-sessions/<session_id>.mode, keyed by the
session_id Claude Code puts in every hook payload and in the statusline's
stdin JSON. The legacy flag stays as a last-write-wins compat mirror and
never receives the literal 'off': an older hook reading that would inject
"CAVEMAN MODE ACTIVE (off)" and an older statusline would render
[CAVEMAN:OFF]. Both spellings of off are read everywhere — a missing file
(old) and a literal 'off' (new, durable).
caveman-config.js gains the primitives: validateSessionId (a whitelist,
because a session id becomes part of a path), resolveActiveMode,
readSessionModeRaw, writeSessionMode, per-session prev for one-shot skills
(#599), gcSessionStore, and a session_id-tagged recordModeChange (#601).
Every one takes a sessionId that may be null or malformed and degrades to
the previous machine-wide behavior — the old code path IS the fallback
branch, which is why the existing suites, nonid,
pass unchanged.
SessionStart re-derives the default only on `startup` and `clear`; compact,
resume, fork, an unrecognized source and the n`
read the stored value. Grouping `clear` with the resets deliberately
differs from #691's comment: per-session storage makes the distinction
cheap, and nothing else in the conversation survives /clear either. The
watchdog must not reset, or a slow payload on a compaction re-arms a
session the user turned off.
The three entrypoints resolve the new helpers individually instead of
demanding them in their requireSibling shape checks (#848): a
caveman-config.js from before this change satisfies those checks, so
hard-failing would trade "machine-wide mode, no
state at all" on exactly the plugin-cache drift that guard exists for.
Both statusline ports take session_id from the stdin JSON with bounded,
TTY-guarded reads (integer timeout — bash 3.2 rejects fractional read -t)
and render nothing for a deactivated session. verify_repo.py greps them
against SESSIONS_DIRNAME and SESSION_ID_RE, tws
badge. caveman-stats filters the transition l
window's switches no longer skew another's es
(bin/install.js, uninstall.sh, uninstall.ps1) removes the session store;
.caveman-history.jsonl is kept on purpose. opide
behaviour — it writes its own flag in its own config dir and never sees a
Claude Code session_id.
Tests: 25 unit cases in tests/hooks/caveman-config.test.mjs,
SessionStartSourceTests, SessionScopedModeTests, 5 statusline cases, and
tests/manual/session-mode-smoke.sh (22 checks, end-to-end against a
throwaway config dir). docs/testing-session-modes.md carries the full plan
including the live-Claude-Code checks. Hook ced.
npm test 197/197 · tests/*.js clean · unittesss
· smoke 22/22
Signed-off-by: Pavel Filippenko <filippenko.ps@phystech.edu>
v2.3.0 bumped bin/install.js and the README/INSTALL one-liners but left
install.sh and install.ps1 pinned at v2.2.0. The curl-pipe path reads the
shim's own default ref, so `curl .../v2.3.1/install.sh | bash` bootstrapped
the previous release. Root package.json had drifted the same way.
Adds a test asserting the three pins, the installer package version, and
every documented one-liner name the same release, so the next bump cannot
miss one silently.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KA6VNr91eQ7MCQAB9E1LVN
Installer pins move to v2.2.0 (install.sh, install.ps1, bin/install.js, and the
one-liners in README/INSTALL). The CLI pins the bin-v1.1.2 runtime that carries
the engine line-ending and proxy cache-epoch fixes. Root installer package
version resyncs to the product tag after drifting at 2.0.0.
@caveman-ai/pi stays 0.1.0 — pi-v0.1.0 is its first release.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmbqWDtNexzDfH5V3BMRz
Two guarantees, layered:
1. --test-force-exit on EVERY node --test invocation in the repo (16
package.json scripts, both engine-ci root-test steps, profiles.yml, and
the test:windows compat runner) — a test file that leaks a server,
socket, or child after a failed assertion now exits when its tests
finish instead of pinning the runner until the workflow limit.
2. timeout-minutes on all six engine-ci jobs (25, or 45 for
typescript/windows) — engine-ci was the only workflow with none, which
is how a single leaked handle occupied a runner for 6 hours, twice.
Anything that still finds a way to wedge now dies as a visible failure
inside the hour.
Verified locally with the flag: root installer 169, subagent-tax 48,
mem/js, mcp, browse, shrink, explorer, caveman-learn — all green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ez7w7BBtKrMZCiZz9ea4cb
Improve Windows support across installer, CLI hooks, binary lookup, hidden child processes, JetBrains detection, and Chrome discovery. Add real Windows ACL checks for SQLite parent directories instead of skipping the security guard.
Also expand BSL-1.1 coverage/documentation for new engine-linked runtime modules (`cacheengine`, `rewriter`, `browse`) and add repo/CI checks so license boundaries and cross-platform test paths stay enforced.
Single-commit port of the Caveman 2 tree onto the original repo history
(candidate caveman-2@88878a4 applied over main@3098342). No unrelated-history
merge; original history untouched.
Preserved v1 surfaces:
- skills/caveman/SKILL.md byte-for-byte, plus plugin mirror and caveman.skill zip
- legacy CI (Node 18/20/22 installer + Python 3.11 suites) alongside engine-ci
- cli/install.js compatibility shim forwarding to bin/install.js
- .codex/config.toml ships both hooks and codex_hooks for older Codex (#617)
- README Trendshift + Skills.sh badges and local star-history asset
- v1.10.0-pinned curl installs; benchmarks/tmp-starcharts ignore rules
Fixed for public same-repo release:
- SECURITY.md vulnerability reporting points at this repository
- release-binaries.yml publishes to this repo via job-scoped GITHUB_TOKEN
(cross-repo PAT removed); binary-release environment still gates signing
- packages/cli/PUBLISHING.md rewritten for packages/ layout and same-repo flow
- profile-contract and provider-catalog lanes skip lane-scope gates on
initial import so the launch PR can gate on their real validation steps
- ANNOUNCEMENT.md network claim corrected: compression is local; requests
still go to the provider you configured
Caveman 2 stays opt-in: bin-v1.0.0 binaries and @caveman-ai/cli publish
separately after this lands and soaks.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BLvb5QniwYjmcawyssQNw9
Marketplace fix (#712, #705): Claude Desktop rejects plugins containing a
top-level bin/ directory, and .claude-plugin/marketplace.json packages the
repo root, so the installer directory is now cli/. Every reference updated
(package.json bin entry + files, shims, docs, tests, caveman-init require
path). Supersedes PR #726.
Security (PR #717 verified): quoteWinArg only quoted on whitespace/quotes,
leaving cmd.exe metacharacters (& | ^ < > % parens) unescaped on the
shell:true Windows spawn path. Attacker-influenced arguments (--with-init
cwd, --with-mcp-shrink value) could chain commands. Trigger regex now
covers the metacharacter set; quoting logic split into a platform-
independent, unit-tested helper.
Also:
- uninstall removes .caveman-active.prev, .caveman-mode-log.jsonl,
.caveman-statusline-suffix, .caveman-nudge-shown; keeps
.caveman-history.jsonl with a printed note; dry-run now says
'would remove' instead of lying (#635, supersedes PRs #693#636)
- Array.isArray guard in rewriteLegacyManagedHookCommands — malformed
hook event no longer crashes the installer mid-run (supersedes PR #646)
- gemini extensions install --consent: the security prompt hung every
piped/non-interactive install forever (#676, part of PR #664)
- OpenClaw skill stamps the real PINNED_REF version instead of hardcoded
1.0.0; new --no-always flag for load-on-demand installs (supersedes
PR #720)
- shims scope NPM_CONFIG_ALLOW_GIT=all to the npx call — npm >=12
defaults allow-git to none and EALLOWGITs github: installs (#698)
- .codex/config.toml ships hooks + codex_hooks keys so auto-activation
works on both sides of the codex-cli rename (#617)
- caveman-help card shows the Windows config path (%APPDATA%) (#723)
- caveman-parse.js added to HOOK_FILES, opencode payload (.cjs), and the
regenerated checksums.sha256; manifest now matches shipped hook
contents — release must bump PINNED_REF to a tag containing these files
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ySX6TBWZuvFze4ajf7Hpf
The caveman.skill ZIP is regenerated by CI on every push and lived at
the repo root, where users mistook it for an editable source. Moving it
to dist/ makes its build-product nature obvious while keeping it
tracked so GitHub release links and npm packaging keep working.
Updates:
- git mv caveman.skill -> dist/caveman.skill
- .github/workflows/sync-skill.yml rebuild step now writes to dist/
- .gitignore ignores dist/* but allows dist/caveman.skill
- package.json files array includes dist/caveman.skill for npm pack
- CONTRIBUTING.md and tests/verify_repo.py path references updated
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>