Skip to content

Installing himmel on your project

Audience: newcomer. Owner of the rewrite: HIMMEL-7212.

The task: protect one git project with himmel, so that a Claude Code session refuses to edit your default branch and works in a worktree instead. Five steps get you there: preview, install, persist, verify, undo. Read the choices, the adapting notes and the recovery path once the first install works.

himmel is a harness for Claude Code: hooks, git gates and guardrails that enforce a PR-gated workflow at the tool-call layer instead of asking the model to remember prose, plus handover files that carry context across sessions.

Developing himmel itself is a different path: new-machine.md.

Before you start

You need two folders: the himmel download, cloned once outside your project, and your project, a git repository you run every install command from.

Terminal window
git clone https://github.com/yotamleo-org/Himmel ~/himmel # outside your project, once
cd <your-project> # the install acts on this folder

Prerequisites, checked by the installer, which fails fast with install hints: git, bash 3.2+, node, jq, python3, and the Claude Code CLI on your PATH. gh is optional for the install but used by the PR and worktree-prune steps; bun is needed only for the optional extras (companion vault tooling, qmd search, the Telegram bridge, armed resume). No node on the machine yet? Run bash ~/himmel/scripts/himmelctl/bootstrap.sh first; it installs Node.

Windows is not supported (HIMMEL-5729). himmel runs on Linux and macOS.

Preview

Run from the root of your project. Both commands write nothing:

Terminal window
node ~/himmel/scripts/himmelctl/bin.js install --scope project --dry-run
bash ~/himmel/scripts/adopt.sh --profile core --scope project --dry-run

The first prints a summary of the install; the second prints one DRY: line for each file and setting it would write. A preview leaves nothing to undo.

Install

Terminal window
node ~/himmel/scripts/himmelctl/bin.js install --scope project # or --scope user

--scope is the non-interactive path: it loads a shipped, runnable preset (docs/setup/profiles/adopter-project.install-profile.json or the adopter-user twin) and installs without asking anything. Drop the flag to get the wizard instead; it asks a handful of questions and lets you confirm each default.

Three words name three different choices, and the installer asks for them separately: the preset is the shipped install-profile file that supplies answers (--scope loads one, --from-profile <file> loads another); the installation scope is where the harness is wired (project or user); the plugin profile is which marketplace plugins are on (lean or full). Detail and the exact values are in What profile means.

The install itself always runs in a terminal. When it finishes it prints a link to the local UI’s Setup page (see ui.md), where you save secrets and set the commit rules: Finish setup in the UI: http://127.0.0.1:<port>/#t=<token>&page=setup. Open the printed link as is; the #t= part is the token. Run the install again while that UI is running and it prints the same link. On a machine with no browser (an SSH session, or Linux with no display) it asks for the secrets in the terminal instead.

Persist: commit it before you branch

A project-scope install writes .claude/, scripts/ and .gitignore into the working tree and leaves them untracked. A worktree is cut from committed history, so one created before the install is committed has none of those files: no .claude/settings.json, no scripts/hooks/block-edit-on-main.sh, no scripts/worktree.sh, and nothing protects an edit there. Commit the install first. Without the pre-commit tool the installer places plain git hooks, and the commit hook refuses a commit on the default branch (with pre-commit present the hooks run only what your .pre-commit-config.yaml lists, which a starter install does not write). Use a branch and fast-forward either way:

Terminal window
git switch -c chore/install-himmel
git add -A
git commit -m "chore: install himmel #1" # the commit-msg gate wants a ticket reference
git switch main
git merge --ff-only chore/install-himmel

Where those plain hooks run, the commit-msg gate rejects a message with no ticket reference. When JIRA_PROJECT_KEY is unset it accepts #<number> (or LOCAL-<number>), as above. On a repository with no ticket convention, switch the requirement off with TICKET_ID_REQUIRED=0 git commit … (or set it in ~/.himmel/himmel.conf, which applies to every project on this machine). On a repository with a branch-protected default branch, push the branch and merge it through a PR instead of the fast-forward. If the project has an origin, the worktree helper cuts new worktrees from origin/main, so the install commit must reach it first, and the installed pre-push hook refuses a direct push to main (no bypass): push chore/install-himmel and merge it through a PR before you create a worktree. A project with no origin branches from the local main. The commands say main; use your default branch’s name if it differs.

.claude/settings.json carries the path of your himmel download (the status line and HIMMEL_REPO point at it), so this commit puts this machine’s path into the project. In a repository you share, a teammate whose download sits elsewhere needs those two values changed to their own path.

A project-scope install also copies and wires the unresolved-review merge gate and its libraries. The install commit adds scripts/ci/ci-trust-paths.txt, seeded only when absent; your existing trust list is never overwritten. Until that list is on your default branch, the gate refuses every merge from a session that loads the new settings, including the adoption PR. The operator must merge that first adoption PR outside Claude, using the forge web UI or their own terminal; no agent session merges it.

A trust path is a file that decides whether other changes are safe: your merge gate, your hooks, your CI checks and the list itself. A change to one can switch a safety check off, so the agent that wrote it may not approve it. The list lives in scripts/ci/ci-trust-paths.txt on your default branch (the copy on a PR branch is never read); add any file that guards your project, such as deploy scripts or release config. You may add lines but never remove one that himmel’s core list (scripts/ci/ci-trust-paths-core.txt in your himmel download) holds. A new list is seeded with every core line; an existing list is left as it is, and a re-install never rewrites it. So check that it holds them, and after a himmel update compare it with the core file, because a PR touching a core path your list lacks is refused (list core-missing <pattern>); copy the missing line back from the core file. A list deleted from the default branch refuses every merge, including the one that brings it back: restore your last full list from git history, so your own lines come back too, add any core line it lacks, and land that commit outside Claude, in the forge web UI or with a raw gh pr merge.

Inside a Claude session, a PR touching a listed path needs a trust-reviewed GO. Only a himmel chief signs one for your repo, and only once your repo has exactly one row in scripts/guardrails/himmel-adopted.json, that row sets "merge_allowed": true (a row with false signs nothing), and an independent judge has recorded its verdict on that head with write-verdict.sh --repo <owner/name>; the chief then runs go.sh --repo <owner/name> --trust-reviewed <judge-qid> <pr> <head>. The file ships empty and is read through the GitHub API from the default branch of the himmel repo that the signing chief’s himmel download names as its origin remote (your own download only when you are that chief), so a row counts only once it is merged there; an edit in a local copy does not count. In solo mode, or until then, such a PR does not merge from a session: the operator merges it in the forge web UI or with a raw gh pr merge, which bypass merge-on-green.sh.

These checks run only inside Claude sessions (the hooks) and in himmel’s merge script (merge-on-green.sh) wherever it is run. A merge made another way, in the forge web UI or with a raw gh pr merge, bypasses them and is not checked. To enforce the list on the forge too, turn on the optional branch rules (CODEOWNERS and a required review for the listed paths; tracked in HIMMEL-3897).

Verify

Open a Claude Code session at the repo root (claude); hooks only fire inside a session. The install must already be committed on the default branch (see Persist). Then:

  1. Try to edit a file while on the default branch. block-edit-on-main should refuse and point you at a worktree. That refusal is the success signal.
  2. Create the worktree: bash scripts/worktree.sh feat/try-himmel. It prints the worktree path (a feat+try-himmel folder under your repo’s worktrees directory) and does not move a running session; quit and run claude again inside that path. Check that .claude/settings.json, scripts/hooks/block-edit-on-main.sh and scripts/worktree.sh exist there. If they do not, the install was not committed first. Edits on the worktree branch then proceed normally.
  3. Try cat .env (if you have one). block-read-secrets should refuse.
  4. Run node ~/himmel/scripts/himmelctl/bin.js status for a severity-grouped diff of what is installed versus what the manifest wants.

Then walk one real loop: getting-started.md.

Undo

A preview wrote nothing. An install is undone by the uninstaller, which also previews first.

Uninstalling

himmelctl uninstall is the inverse of install, and it previews first with --dry-run. Without a terminal, pass uninstall --yes: a run without it aborts and removes nothing. Your Telegram pairing and the install record stay unless you add --purge-state, which is refused while the bridge is still running. The commands, what it removes and what it keeps on purpose: Undo the install. Every file, hook and setting an install writes, and what uninstall does with each one: What changes on your machine.

After the first success

Everything below is for when the five steps above have worked once: what the choices mean, what to configure, and how to fit himmel to a repo with its own conventions.

What profile means: three unrelated things

This is the single biggest comprehension trap in himmel’s surface, so it gets named once, and every later mention in this guide is qualified.

SenseValuesWhere it livesWhat it decides
Install profilestarter, luna, operator, custom — resolving to the manifest categories core, luna, allscripts/install/manifest.json membershipWhich items the installer wants present
Saved install-profile fileany *.install-profile.jsondocs/setup/profiles/, or the wizard’s own cacheYour recorded answers — scope, install profile, plugin set, release channel — replayable with --from-profile <file>
Plugin profilelean, fullscripts/lanes/plugin-profiles.json, toggled with /profileWhich marketplace plugins are enabled in your Claude Code sessions

They are independent. A starter install profile with the lean plugin profile, recorded in a saved install-profile file, is the ordinary adopter setup, and is exactly what --scope project gives you.

The plugin profile toggles with /profile; its registry is scripts/lanes/plugin-profiles.json. The always-on base carries lean-skills@himmel (vendored workflow skills) and i-have-adhd@himmel (vendored; the installer and updater create its flag file unless HIMMEL_NO_ADHD_ALWAYS=1). design adds the HTML/UI design kit and domain-modeling@himmel per dispatch, user enables the opt-in pstack@himmel, and humanizer@himmel is in no plugin profile.

What you are opting into

Every file, hook and setting these choices write, where each one goes and what uninstall does with it: What changes on your machine.

Four independent axes; pick one value from each. Nothing here is a package deal, and every guard has an off switch (see configuration.md for the full map).

AxisChoiceWhat landsWhat it requiresWhat it costs
ScopeprojectThe harness is wired in this repo’s .claude/settings.jsonNothing extraEveryone who clones the repo inherits it — commit the file deliberately
userThe harness is wired in ~/.claude/settings.jsonNothing extraApplies to every project you open on this machine, including ones you did not mean to gate
Install profilestarter → core41 items: hooks, git gates, worktree commands, statusline (claude-hud, cache-expiry line on; legacy bash bar stays opt-in/rollback — tooling-catalog.md), marketplace plugins, the Jira CLI buildgit, bash, node, jq, python3The PR-gated workflow becomes the only way to commit — that is the point, and it is the thing to try on one repo first
lunacore plus the second-brain surface: vault scaffold, qmd index, graphify MCP, the clip cadencesbun, plus disk for the qmd indexA vault you now have to maintain; the cadences run on a schedule
operator / customall (55 items) / whatever you answerEverything aboveThe maintainer’s full machine — not an adopter starting point
Release channelunset (no recorded channel)git pull --ff-only on your branchNothingYou track the tip, including work in progress — this is the fallback, not what a fresh install records
stableThe highest vX.Y.Z tagNothingYou update only when a release is cut
preThe highest tag including -pre.NNothingPrereleases, so earlier fixes and earlier breakage
Plugin profileleanThe everyday plugin setNothingFewer skills loaded, less context spent per session
fullEvery marketplace pluginNothingMore context per session for surfaces you may never invoke

Item membership is data, not prose. Read scripts/install/manifest.json rather than trusting the counts above if they ever disagree. Channels are defined once, in updating.md; this table cites them rather than restating the resolution rules. Two senses of “default” that are easy to conflate: the unset row is what the resolver falls back to when nothing is recorded, while install on a new station records stable, so a fresh install tracks releases, and only a station whose channel was never written tracks the tip.

Minimal config

  • USER_SLUG: your kebab-case handle. Skip it and himmel derives one from git config user.name. That is the whole required configuration.
  • Everything else is opt-in. The companion vault, Telegram, hermes and Jira are all absent until you configure them; the harness runs fully without any of them. Add Jira later by filling the JIRA_* values in .env; the local CLI needs four values and no cloud ID.
  • Handover state: run /handover-setup once to say where cross-session notes live: an inline folder in the repo, or a separate git repo via HANDOVER_DIR. A fresh clone has no handover root until you do. Until then handover_root fails closed with a diagnostic (scripts/lib/handover-path.sh), it does not crash, and nothing silently writes to the wrong place.

Adapting himmel to a repo that is not himmel

himmel’s gates encode himmel’s own conventions. Six of them will not fit your repo unchanged. Each is what it assumes → how to change it → how to turn it off.

1. Ticket IDs in commit messages. check-commit-msg.sh requires every commit subject to reference a ticket. It assumes a Jira-style key: with JIRA_PROJECT_KEY set, the pattern is <KEY>-<number>; with no key set it falls back to #<number>, which is a GitHub issue reference and probably already right for you. To use your own convention, set TICKET_ID_PATTERN to an extended-regex. To switch it off entirely, set TICKET_ID_REQUIRED=0. The conventional-commit shape is still enforced, only the ticket reference is dropped.

2. Attestation trailers. Two pre-push gates ask you to attest, in the commit message, that you did something a machine cannot verify: Platforms tested: <os> on shell and script diffs (scripts/hooks/check-platforms-tested.sh) and Security reviewed: <token> on non-docs code (scripts/hooks/check-security-reviewed.sh). Both belong in the first commit of a branch, written after actually testing and reviewing: the gate fires at push, far too late to teach the habit. They are opt-in for adopters: they run only if you list the platforms-tested and security-reviewed hook ids in your own .pre-commit-config.yaml. Omit the ids and neither ever fires.

3. .single-writer: the one you will want on day one. block-edit-on-main refuses edits when HEAD is your default branch, which is correct for a review-gated team repo and wrong for a personal repo you commit straight to. Drop a .single-writer file at that repo’s root (touch .single-writer) and on-main edits are allowed there. It is local and gitignored, so clones of the same repo stay protected; this is a per-checkout opt-out, not a repo policy.

4. The worktree workflow, and where the off switch goes. /worktree creates an isolated branch and worktree next to your .claude/ directory; block-edit-on-main and check-worktree-isolation keep work inside one. For a deliberate one-off, the off switch is EDIT_ON_MAIN_OK=1, and it must be set in the shell that launches Claude Code (EDIT_ON_MAIN_OK=1 claude). A per-call prefix inside a session does nothing, because the hook runs in a process that never sees it. This is the most common adopter confusion; it is worth reading twice.

5. Which guards you may disable, and which you should not. Disabling is per-hook: remove the stanza from the .claude/settings.json the installer wired, or the hook id from .pre-commit-config.yaml.

  • Reasonable to disable: the ticket-ID requirement, the attestation trailers, the doc-freshness warn-only hook, and the backend-routing guard (block-backend-tier.sh) if you have no Jira.
  • Keep: block-read-secrets.sh (stops .env, *.pem, id_rsa and friends from reaching the model as tool results) and block-destructive-commands.sh. These are the two that fail closed around irreversible harm, and they cost nothing when nothing dangerous is happening.
  • The himmel-guard plugin is on by default. The starter install enables himmel-guard@himmel, which carries the same destructive-command guard as a plugin. To opt out run /profile disable himmel-guard@himmel; the plugin README covers keeping it off across a reconcile.

6. Operator-personal subsystems: skip all of these. None of them is part of the portable core, and skipping them changes nothing about the hooks or the worktree workflow.

SubsystemWhat it isSkip signal
Jira (scripts/jira/)Local Jira CLI plus the backend-routing guardAbsent without JIRA_* credentials; the guard never fires
luna / Obsidian (scripts/luna/)Vault management and the clip pipelineNot in the core install profile at all
Telegram (scripts/telegram/)Remote dispatch and chat bridgeAbsent without a bot token
hermes (scripts/hermes/)Free-inference junior and review lanesAbsent without API keys
graphify (scripts/graphify/)Knowledge-graph retrieval over a corpusOptional; its data egress is fenced by scripts/guardrails/egress-matrix.json
AFK (unattended) mode (scripts/afk/)Unattended multi-ticket dispatchDepends on handover plus Jira

Lifecycle

You want to…Run
See what driftednode ~/himmel/scripts/himmelctl/bin.js status
Fix what driftednode ~/himmel/scripts/himmelctl/bin.js ensure
Change scope after the factnode ~/himmel/scripts/himmelctl/bin.js scope set <project|user>
Update the harness/himmel-update (Claude Code’s own autoUpdate does not deliver himmel)
Diagnose a sick install/himmel-doctor (--fix repairs the node wiring)
Preview an offboardnode ~/himmel/scripts/himmelctl/bin.js uninstall --dry-run (or -n)
Offboardnode ~/himmel/scripts/himmelctl/bin.js uninstall (add --yes when there is no terminal to confirm)

Detail for all of these: updating.md. Coming from an older himmel install rather than a fresh one: migrating.md.

Appendix — manual install (recovery only)

Use himmelctl install. This sequence exists for one situation: the installer cannot run and you need the portable core anyway. If you end up here, that is a bug worth reporting, not a supported second path, and /himmel-doctor is the first thing to try instead.

The portable core needs bash 3.2+, git, jq, python3. Copy these files from the himmel clone into your repo, keeping the relative paths (the hooks source their libraries relative to their own location):

scripts/hooks/auto-approve-safe-bash.sh
scripts/hooks/block-edit-on-main.sh
scripts/hooks/block-read-secrets.sh
scripts/hooks/lib/guard-unwrap.sh
scripts/hooks/lib/shell-tokenize.sh
scripts/guardrails/lib.sh
scripts/lib/py-armor.sh
scripts/clean-garden.sh
scripts/worktree.sh
scripts/clean.sh
scripts/_new-worktree.sh

Make them executable, then wire the three hooks into your repo’s .claude/settings.json:

{
"hooks": {
"PreToolUse": [
{ "matcher": "Bash",
"hooks": [{ "type": "command", "command": "bash $CLAUDE_PROJECT_DIR/scripts/hooks/auto-approve-safe-bash.sh" }] },
{ "matcher": "Edit|Write|MultiEdit|NotebookEdit",
"hooks": [{ "type": "command", "command": "bash $CLAUDE_PROJECT_DIR/scripts/hooks/block-edit-on-main.sh" }] },
{ "matcher": "Bash|PowerShell|Read|Grep",
"hooks": [{ "type": "command", "command": "bash $CLAUDE_PROJECT_DIR/scripts/hooks/block-read-secrets.sh" }] }
]
}
}

CLAUDE_PROJECT_DIR is set by Claude Code to the directory holding .claude/settings.json, so that directory must be your repo root. Verify with the four steps under Verify above, then run node ~/himmel/scripts/himmelctl/bin.js status as soon as the installer works again. It will tell you what the manual copy missed.

Where to go next

  • getting-started.md — your first PR-gated loop.
  • daily-loop.md — one full loop with every gate explained where it fires.
  • configuration.md — every knob, every gate’s classification, every off switch.
  • migrating.md — converging an older himmel install.
  • Credits — the sources, authors, licences and pins behind the plugins, skills and forks.