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>
`npm publish dist/x.tgz` has no leading ./, so npm parses the slash as an
owner/repo shorthand and tries `git ls-remote ssh://git@github.com/dist/x.tgz.git`.
pi-v0.1.0 was the first tag to reach this job — it has been broken for every
npm package since the workflow was written.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmbqWDtNexzDfH5V3BMRz
zip -r adds to an existing archive and dist/caveman.skill is tracked, so a file
deleted from skills/caveman/ never left the shipped ZIP. Delete before zipping.
release-packages also triggers on pi-v*, and verify_repo checks all six mirrors
sync-skill.yml writes instead of only the first.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmbqWDtNexzDfH5V3BMRz
pi-enable and pi-wrap could not join the Windows gate: their fixtures built
PATH with `:` and wrote extensionless `#!/bin/sh` stubs, which Windows cannot
execute at all. tests/harness/stub-bin.mjs gives them two stub shapes, one
per way the CLI launches things — a .cmd Node shim for commands routed
through portableInvocation (`pi`), and, for the Go binaries the CLI
execFile's directly, a real .exe: node.exe hardlinked under the binary's
name, answering from a --require preload that runs before Node resolves
argv[1] as a script. A .cmd cannot serve there, since Node refuses to spawn
one without a shell (CVE-2024-27980). Both fixtures also set USERPROFILE
beside HOME — os.homedir() reads it on Windows, and the Pi extension
directory hangs off it — and retry cleanup for handles Windows still holds.
update.runtime.mjs stays out, and not for a harness reason: its committed
checksum manifest carries only darwin and linux artifacts, so on win32
`update` looks up caveman-proxy_win32_amd64 and fails before downloading
anything. Adding those rows needs the release signing key; the regeneration
recipe is recorded at the fixture read.
The macOS job also runs `go test ./engine/... ./proxy/... ./shared/...` —
without it the runner touched no Go at all, so a BSD-only path, spawn or
timer regression shipped green on the platform this job exists to cover.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmbqWDtNexzDfH5V3BMRz
Recovery client. ensure() assigned probed/child only after an await, so N
concurrent callers ran N probes and N spawns and tracked only the last child
— the rest stayed alive as orphans holding ccr.db. One in-flight bring-up is
now shared, dispose() reaps a child that lands late, and a child that fails
or never finishes the handshake is reaped instead of left for the next spawn
to collide with. The handshake also stops borrowing the 30s retrieve budget:
it is awaited inside session_start, so an MCP that starts but never answers
initialize froze Pi startup for the full 30s. 2s, then the session degrades
to "no recovery available".
caveman_retrieve output is no longer shrunk. Its result IS the recovered
original, so masking it hands the model a fresh ccr:// handle and recovery
loops instead of terminating — and registering the tool disables the proxy's
server-side retrieve loop, so nothing else would strip it.
pending context from tool calls accumulates across a whole turn and drains
only at the next before_agent_start; MAX_CONTEXT_BYTES capped one response,
not the join, so a tool-heavy turn built an unbounded system prompt. It now
caps the accumulation.
The stamped hook argv is tried first but no longer alone: it bakes an
absolute [node, …/dist/index.js] at enable time, so an nvm switch made every
hook call ENOENT and the session degraded to permanent silent direct mode.
PATH candidates follow it. Both copies of resolveWindowsCommand (here and in
the CLI) now apply PATHEXT to a *path* too — returning the extensionless
~/.caveman/bin/caveman straight to execFile is the EFTYPE they exist to
prevent.
Release: `pi-v*` tags publish @caveman-ai/pi, with a smoke check that the
tarball actually carries the bundled extension package.json names, plus the
registry-resolved package-lock.json `npm ci` needs (the one generated inside
the pnpm workspace linked to ../../node_modules/.pnpm and would have failed
on a clean checkout). README gains the Pi wrap row (eight native agents) and
drops the Sponsors section.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmbqWDtNexzDfH5V3BMRz
Two red jobs that had nothing to do with the platform they were failing on.
node (18): package.json's "test" script passes --test-force-exit, added in
7a4cf50 to make suite hangs structurally impossible. That flag needs Node
>=20.14, so on 18 the suite did not hang — it refused to start
("node: bad option"). Node 18 is still a supported runtime (engines ">=18";
bin/install.js refuses to run below it), so rather than drop the version we
claim to support, run the suite flagless there. Verified locally on 18.20.8:
172/172 pass, no hang, and all ten tests/test_*.js suites pass too. The job
timeout is the backstop the flag would otherwise provide.
engine-ci windows + typescript: both failed at `pnpm install --frozen-lockfile`
with ERR_PNPM_OUTDATED_LOCKFILE — packages/pi-extension was added to the
workspace but never entered pnpm-lock.yaml, so the Windows job died at step one
without running a single Windows test. Regenerated with --lockfile-only;
`pnpm install --frozen-lockfile` now succeeds.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ez7w7BBtKrMZCiZz9ea4cb
Whole-repo review of the surfaces that mutate a user's agent config, plus a
cross-platform sweep focused on Windows.
Hooks (Windows):
- caveman-mode-tracker finished its work in ~50ms then blocked until the host
killed it at the 5s budget: the 'data' listener REFERENCES the stdin handle,
so pause() alone keeps the event loop alive until the host closes the write
end, and on Windows that close lags arbitrarily. Reproduced off-Windows.
This is the shape of #819. Fixed with process.stdin.unref().
- caveman-activate used readFileSync(0), which blocks INSIDE the read syscall
until EOF — no deadline can interrupt it. The #729/#833 fix had been applied
to the tracker and never to its sibling. Rewritten event-driven, acting on
the first complete payload, with a watchdog so a payload that never arrives
still activates instead of forfeiting the session.
- hookCommand emitted PowerShell call-operator syntax into settings.json, but
Claude Code's Windows hook shell is Git Bash, which rejects it outright:
`syntax error near unexpected token '&'`. Every standalone Windows install
had two dead hooks (#835). Both emitters now route through one bash-safe
shape.
- activate now threads the payload cwd, so a checked-in .caveman.json
defaultMode "off" is honored for the session's directory (#634), and a JSONC
settings.json no longer suppresses the statusline nudge.
Config ownership:
- src/hooks/uninstall.{sh,ps1} matched hooks by the substring "caveman" and
filtered whole entries, deleting user hooks that merely mention the word and
dropping foreign handlers that shared a matcher group (#593). Ported the
exact-basename matcher from bin/lib/settings.js and switched to filtering the
inner handler list. Verified against real PowerShell: the old code left zero
user hooks standing.
- install.{sh,ps1} backed up settings.json unguarded, so a --force reinstall
overwrote the only pre-caveman copy with the merged file. Back up once.
- OpenClaw overwrote a hand-edited workspace skill with no backup.
- caveman-init appended unfenced blocks to tracked repo files, so they could
be neither refreshed nor removed; now marker-fenced and written atomically.
- opencode AGENTS.md and OpenClaw SOUL.md were presence-checked, so a ruleset
edit never reached anyone already installed. Both refresh in place.
- opencode's system-prompt hook had no idempotency guard; it now rewrites its
line rather than stacking another onto the context window.
- readSettings reports when it stripped comments so both write paths warn and
point at the backup instead of silently dropping a user's annotations.
Skills: skills/ is auto-discovered wholesale by the plugin loader, so 13
undocumented skills would have shipped into every user's agent on the next
release. Documented them and added a verify_repo check that fails when a
skills/ directory is named in neither README.md nor CLAUDE.md.
CI: a windows-powershell job covering the surface engine-ci never exercised —
the PowerShell installer round trip, and a bash -n gate over every hook command
the installer writes. That gate alone catches #835.
Tests: every fix above is pinned by a test that fails against the old code,
including tests/test_hook_stdin_lifecycle.js, which reproduces the Windows
lagging-close condition on any platform.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ez7w7BBtKrMZCiZz9ea4cb
Findings from a whole-repo cross-platform audit.
Data loss:
- evals/llm_run.py wrote the snapshot with write_text(ensure_ascii=False) and
no encoding: on Windows that truncates results.json to 0 bytes, then raises
at the first non-cp1252 char. Destroyed the committed snapshot and the run.
- compress.py read the user's target with errors="ignore", silently dropping
undecodable bytes, backing up the mangled text, passing its own readback
check, then overwriting the original. Now decodes strictly and refuses.
Broken on Windows:
- benchmarks/ and evals/ read SKILL.md, README.md and results.json without an
encoding; all three raise on a cp1252 decode.
- cavemem's Python client used text=True with no encoding, so a non-ASCII
memory failed to store and any recall came back mojibake.
- native-hook-fast spawned a second full Node process from inside a hook with
no timeout at all.
Also fixed:
- Backported the pnpm cross-drive shim fix to the five copies that never got
it; hoisted two inline parsers so all of them are unit-testable.
- portable-command now resolves through PATH/PATHEXT instead of silently
no-op'ing on a bare command name.
- uninstall.ps1 writes a temp .js instead of node -e (matches install.ps1).
- Real backoff on the flag rename retry; EPERM/EBUSY retry when replacing a
running binary.
- Hooks act on the first complete JSON payload instead of blocking on EOF;
the stats child timeout drops below the host's hook budget.
- Statusline tests run the .ps1 on Windows instead of skipping.
- Added the macOS CI job the repo never had.
Left unfixed and annotated in place: the win32 hookCommand emits PowerShell
call-operator syntax while #835 describes the runner as git-bash /bin/sh.
Needs a real Windows host to settle; guessing would break whichever works.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NveRZs9mV97iX7HSgq3BYF
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
The cli exclusion let the step run to completion: every other workspace
suite passes on Windows (graders 381, sdk/ts 190, mastra 40, subagent-tax
45, kit, create-agent, shrink, browse, mcp, mem/js, learn, ui). The one
remaining failure block is @caveman-ai/agent (42) — a historical copy whose
source of truth is caveman-agent-sdk, with Windows contracts already in the
curated test:windows gate.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ez7w7BBtKrMZCiZz9ea4cb
The windows job finally reached pnpm -r test and surfaced 150 failures:
149 are the whole @caveman-ai/cli runtime suite, which writes POSIX shebang
stub agent binaries and has never run on Windows — porting that harness is
a separate project, and the CLI's Windows contracts already run in the
curated test:windows gate later in the same job. Excluded with a comment
naming the condition to remove the exclusion. The 150th was shrink's
package test spawning bare npm — fixed portably like mcp/browse.
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.
Remove broken internal links, add shrink package coverage, pin CI actions, skip Go-backed JavaScript tests when Go is absent, and surface hook cleanup failures in debug mode.\n\nRefs #825
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
Tests existed but nothing ran them in CI. Node 18/20/22 matrix runs the
installer suite plus every standalone tests/test_*.js runner; a python job
runs unittest discover (compress tests mock the Claude call, no network).
Node is set up in the python job too — several python tests shell out to
node for hook checks. Inspired by PR #695, minus the scanner extras.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ySX6TBWZuvFze4ajf7Hpf
Drop CI-generated mirrors that self-applied caveman to this repo when
opened in Cursor/Windsurf/Cline/Copilot. The installer never read them
(bin/install.js consumes only hooks/, rules/, tools/, agents/, skills/,
mcp-servers/, plugins/caveman/), so removing them just declutters the
root. Devs who want self-application can opt in via npx caveman --only
<agent>.
Removed:
- .cursor/skills/, .cursor/rules/caveman.mdc
- .windsurf/skills/, .windsurf/rules/caveman.md
- .clinerules/caveman.md
- .github/copilot-instructions.md
- caveman/SKILL.md (and empty caveman/ parent)
Updated .github/workflows/sync-skill.yml: drop the cp + mkdir lines and
git add paths for those mirrors, drop the entire "Sync auto-activation
rules" step, and drop rules/caveman-activate.md from the paths: trigger
since the workflow no longer consumes it. plugins/caveman/, cavecrew,
compress, and caveman.skill ZIP sync steps are preserved.
Updated CONTRIBUTING.md note to reflect the smaller auto-synced set.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Move caveman-compress/ source (SKILL.md, scripts/, README.md, SECURITY.md) to skills/caveman-compress/.
- Delete skills/compress/ — the CI-generated rename mirror that caused dual-source confusion.
- Move plugins/caveman/skills/compress/ to plugins/caveman/skills/caveman-compress/. Plugin keeps the consolidated name; CI no longer rewrites the frontmatter.
- Replace the two sed-heavy CI sync steps with one verbatim cp -r from source to plugin.
- Update verify_repo.py, test_compress_safety.py, test_validate_inline.py, GEMINI.md, AGENTS.md, CONTRIBUTING.md, README.md, and the workflow paths to reference the new location.
- Use Path.resolve().parents[N] for the benchmark.py repo-root walk now that the directory depth changed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
install.sh:
- detect_match: replace `awk -v RS='||'` (rejected by macOS BSD awk:
"illegal primary in regular expression") with bash parameter expansion.
Compound detection specs were silently failing, so cursor / windsurf /
continue / and 28 other compound-spec providers were never detected.
- --with-mcp-shrink: flip default OFF + probe `npm view caveman-shrink`
before registering. Was registering a config that 404s on first spawn.
install.ps1: mirror the MCP-shrink default flip + npm probe.
tests: update statusline tests for default-on suffix behavior. Add a
regression for fresh installs where the suffix file is absent.
Add npm-pkg-fix formatting to package.json.
cavecrew: promote agents/cavecrew-*.md to top-level canonical, refine
subagent contracts (sharper output formats, terminal refusal
lines, model: haiku for read-only roles). CI workflow syncs
agents/ + skills/cavecrew/ into plugins/caveman/.
docs: README & CLAUDE.md align with new opt-in MCP-shrink policy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Gemini CLI fails to load skills through symlinks. Replace symlinks
in skills/compress/ with actual file copies and update CI to keep
them in sync. Remove redundant root compress/ directory.
Symlinks in plugins/caveman/skills/compress/ were checked out as plain
text path stubs on Windows (core.symlinks=false), causing Codex to reject
SKILL.md as missing YAML frontmatter. Replace with verbatim copies of
source files and add CI sync step to keep them in sync.
Closes#92
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add .windsurf/skills/caveman SKILL.md and update the sync workflow to copy and commit the new file. Expand README install docs with agent-specific install instructions and standalone hook usage; update AGENTS.md and GEMINI.md to use the new caveman-compress path. Add Windows PowerShell installers (hooks/install.ps1 and hooks/uninstall.ps1) and enhance shell installers (hooks/install.sh, hooks/uninstall.sh) to support --force, plugin detection, clearer statusline wiring messages, and idempotent JSON merging/removal of hook entries. Provides uninstall guidance for other agents and improves overall install/uninstall robustness.
Add project-level Cursor skill discovery support by introducing
`.cursor/skills/caveman/SKILL.md` as a copy of the canonical
`skills/caveman/SKILL.md`, without changing skill behavior.
Update the sync workflow to keep the new Cursor copy aligned with the
canonical source alongside existing synced targets, and include it in the
CI commit step. Also update CONTRIBUTING notes to document that this new
path is auto-synced and should not be edited directly.
Update .github/workflows/sync-skill.yml to add a pre-change step that configures git user/email and runs git pull --rebase origin main before copying files, reducing risk of merge conflicts. Also rename the commit step to "Commit and push if changed" and remove redundant git config and pull from that step so it only commits and pushes the updated files.
Update GitHub Actions workflow to add concurrency controls, checkout the main ref, simplify ZIP creation (zip directly from skills/), and pull --rebase before committing; commit message now includes [skip ci]. Also update README to add an "Intensity Levels" nav link. Clarify skills/caveman/SKILL.md: document explicit /caveman lite|full|ultra triggers, expand the description of dropped filler/pleasantries/hedging, and apply small formatting tweaks.
Add GitHub issue templates for bug reports and feature requests tailored to the caveman persona, and add CONTRIBUTING.md describing how to propose changes to skills/caveman/SKILL.md (fork, edit, PR with before/after examples and rationale). These files help standardize contributions and make it easier for maintainers to review small, focused changes; also points contributors to ‘good first issue’ tasks.