How Caveman Installation Hooks Manage File Ownership and CI Synchronization

Caveman installation hooks enforce file-ownership safety through atomic writes with symlink protection and UID verification in src/hooks/caveman-config.js, while the .github/workflows/sync-skill.yml CI workflow automatically mirrors authoritative source files from src/hooks/ into the plugins/caveman/ distribution to keep installations synchronized across all agents.

The Caveman project provides a defensive hook system for Claude Code that requires rigorous file permission controls and automated distribution management. Understanding how Caveman installation hooks manage file ownership and CI synchronization reveals an architecture designed to prevent privilege escalation attacks while maintaining consistency across development environments.

Defensive File-Ownership Checks in the Hook Installer

The installation process centers on three hook files (caveman-activate.js, caveman-mode-tracker.js, and the status-line badge) deployed via src/hooks/install.sh. Every write operation incorporates ownership verification to prevent cross-user attacks.

The safeWriteFlag() function in src/hooks/caveman-config.js (lines 31-41) implements atomic file creation to prevent race condition exploits. The function creates temporary files using O_NOFOLLOW | O_EXCL flags, ensuring the system throws an error if a symlink exists at the target location. It then commits the write via fs.renameSync, which is an atomic operation on POSIX systems. This eliminates the window where an attacker could swap a file for a malicious symlink between creation and write operations.

Cross-Platform UID Verification

Before writing the mode flag to $CLAUDE_CONFIG_DIR/.caveman-active, the code resolves the parent directory using fs.realpathSync and compares the directory's UID to the current process UID (lines 43-56). If the UIDs differ, the function aborts immediately, preventing a cross-user symlink attack where a malicious user might own the directory path. On Windows systems where UID checks are unavailable, the implementation falls back to path traversal verification, ensuring the resolved directory lives within the user's home tree and rejecting any path that escapes it (lines 57-65).

Idempotent Installation Flow

The src/hooks/install.sh script implements idempotent checks (lines 48-86) that verify hook file presence and verify they are properly wired into ~/.claude/settings.json before making changes. If the script detects that ALL_FILES_PRESENT and HOOKS_WIRED conditions are satisfied, it exits early unless the --force flag is supplied. This guarantees that subsequent CI-driven updates will not produce duplicate entries in the user's configuration.

Automated CI Synchronization of Hook Files

Caveman maintains a strict separation between source-of-truth files and distribution artifacts. The authoritative hook logic lives in src/hooks/, while the runtime distribution resides in plugins/caveman/.

The sync-skill.yml Workflow

The GitHub Actions workflow defined in .github/workflows/sync-skill.yml monitors the main branch for changes to skills/**/*.SKILL.md and agents/cavecrew-*.md. When changes are detected, the workflow triggers automatically to rebuild the plugin distribution. The workflow commits generated mirrors back to the repository with a [skip ci] message to prevent infinite automation loops.

Mirroring Source-of-Truth to Plugin Distribution

The CI workflow performs three critical synchronization steps:

  1. Copies canonical skill markdown files from skills/caveman/ into plugins/caveman/skills/
  2. Copies the caveman-compress skill and auxiliary scripts into the plugin tree
  3. Copies agents/cavecrew-*.md files into plugins/caveman/agents/
  4. Rebuilds the release ZIP at dist/caveman.skill

Because the installer only appends missing entries to settings.json and verifies file presence before copying, these CI-generated mirrors can safely overwrite stale hook files without destroying user customizations or triggering duplicate installations.

Integration: How Ownership Safety and CI Sync Work Together

The defensive design ensures that fresh installs and CI updates coexist safely. When a user runs the installer, it copies hook files into ~/.claude/hooks/ and writes the mode flag using safeWriteFlag(), guaranteeing ownership by the current user. Later, when CI updates the plugins/caveman/ mirror, the same hook files are updated in the distribution. Because the installer's idempotent checks detect existing installations, users can safely pull CI updates without re-running the installer, while new users receive the latest synchronized version automatically.


# Install the hooks (idempotent operation)

$ bash src/hooks/install.sh

# Force reinstall to overwrite existing files

$ bash src/hooks/install.sh --force

# Verify ownership of the flag file after install

$ ls -l ~/.claude/.caveman-active
-rw------- 1 $(whoami) $(whoami) 4 Sep 12 14:23 /home/you/.claude/.caveman-active
// SessionStart entry added by install.sh to ~/.claude/settings.json
{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "node \"${HOME}/.claude/hooks/caveman-activate.js\"",
            "timeout": 5,
            "statusMessage": "Loading caveman mode..."
          }
        ]
      }
    ]
  }
}

# Excerpt from .github/workflows/sync-skill.yml

on:
  push:
    branches: [ main ]
    paths:
      - 'skills/**/SKILL.md'
      - 'agents/cavecrew-*.md'

jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Mirror skills to plugin
        run: |
          cp -r skills/caveman plugins/caveman/skills/
          cp agents/cavecrew-*.md plugins/caveman/agents/
      - name: Rebuild ZIP
        run: node bin/install.js
      - name: Commit mirrors
        run: |
          git add plugins/caveman
          git commit -m "Sync plugin mirrors [skip ci]"
          git push

Summary

  • Atomic writes prevent symlink attacks: The safeWriteFlag() function in src/hooks/caveman-config.js uses O_NOFOLLOW | O_EXCL and atomic renames to eliminate race conditions during file creation.
  • UID verification enforces ownership: The code compares directory UIDs to the process UID before writing to $CLAUDE_CONFIG_DIR/.caveman-active, with Windows fallbacks to home-directory path validation.
  • CI automation maintains distribution: The .github/workflows/sync-skill.yml workflow mirrors src/hooks/ and skill files into plugins/caveman/ automatically on every push to main.
  • Idempotent installation prevents conflicts: The src/hooks/install.sh script checks for existing installations before modifying settings.json, allowing CI updates to overwrite distribution files without duplicating user configuration entries.

Frequently Asked Questions

Caveman prevents symlink attacks through the safeWriteFlag() function in src/hooks/caveman-config.js, which creates temporary files with O_NOFOLLOW | O_EXCL flags before renaming them atomically. The function also resolves the parent directory using fs.realpathSync and verifies the directory UID matches the current process UID before any write operations occur.

What happens if the CI workflow overwrites my local hook files?

The CI workflow updates the plugins/caveman/ distribution directory, not your local ~/.claude/hooks/ installation. If you installed hooks via src/hooks/install.sh, the script's idempotent checks detect existing files and skip duplication. You can safely pull CI-updated distributions without affecting your active hook configuration in settings.json.

Why does the installer check file ownership before writing the flag?

The ownership check prevents privilege escalation attacks where a malicious user might create a symlink in $CLAUDE_CONFIG_DIR pointing to a sensitive file owned by another user. By verifying that the target directory's UID matches the current process UID (or on Windows, that the path remains within the home directory), Caveman ensures it only writes to directories the user actually controls.

How can I verify that my Caveman installation is synchronized with the latest CI build?

Check that your local hook files in ~/.claude/hooks/ match the source files in src/hooks/ (specifically caveman-config.js, caveman-activate.js, and caveman-mode-tracker.js). The CI workflow keeps plugins/caveman/ synchronized with these sources, so running bash src/hooks/install.sh --force will reinstall from the latest distribution if you need to resynchronize manually.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →