codealmanac install audit

Historical hook-era audit. Current auto-capture is scheduler-backed: almanac automation install registers the scheduled almanac capture sweep job, and setup/uninstall clean old hook entries privately.

Version examined: 0.1.5 (from https://github.com/AlmanacCode/codealmanac.git, cloned 2026-04-16). Context: User ran npx codealmanac in /Users/kushagrachitkara/Downloads/reverie/openalmanac. Bootstrap and capture hook produced output; almanac is not on PATH; which almanac returns nothing. Scope: Install surface only. The wiki data model, capture/writer/reviewer agents, and query CLI design are out of scope — those work.


TL;DR#

Three real problems, compounding:

  1. The README advertises npx codealmanac as "installs globally + runs the setup wizard." The wizard does not install anything globally. It installs a hook, copies guide files, and edits ~/.claude/CLAUDE.md. The almanac binary stays inside npx's per-invocation cache and is never linked onto PATH.
  2. The SessionEnd hook written into ~/.claude/settings.json is an absolute path into npx's content-addressed cache (~/.npm/_npx/<sha>/node_modules/codealmanac/hooks/almanac-capture.sh). That <sha> is a hash of the resolved dependency tree for the exact version that was run. A future version bump, an npm cache GC, or a npm cache clean silently invalidates the hook and captures stop firing.
  3. The setup wizard's side-effect model is inverted: it performs globally persistent modifications to the user's Claude config (hook, CLAUDE.md import, two guide files in ~/.claude/) while refusing to do the one locally-reversible thing (install itself on PATH). The things that require consent are automated; the thing that could be automated is skipped.

Evidence#

Evidence 1 — the README claim#

README.md:22 states:

npx codealmanac                # installs globally + runs the setup wizard

This is not what happens. Searching the entire src/ tree for any call that would install the package globally:

grep -rn 'npm i -g\|spawn.*npm\|install.*codealmanac' src/

returns zero hits in src/commands/setup.ts. The only references to npm i -g anywhere in the source are:

  • src/commands/update.ts:17 — the separate almanac update command, which does shell out to npm i -g codealmanac@latest but is invoked explicitly by the user, not by setup.
  • src/commands/doctor.ts:196 — a diagnostic string printed when the binary isn't detectable: "reinstall with: npm install -g codealmanac". Doctor knows the install can end up in a broken state; setup doesn't prevent it.

So setup.ts — the thing that runs when you invoke bare codealmanac or npx codealmanac — installs the hook, the guides, and the CLAUDE.md import, and that is all. The package binary is wherever npx put it.

Evidence 2 — the hook path on this machine#

~/.claude/settings.json (this user, post-install):

{
  "SessionEnd": [
    {
      "matcher": "",
      "hooks": [
        {
          "type": "command",
          "command": "/Users/kushagrachitkara/.npm/_npx/1123f5c16135f0b0/node_modules/codealmanac/hooks/almanac-capture.sh",
          "timeout": 10
        }
      ]
    }
  ]
}

The directory 1123f5c16135f0b0 is npm's content-addressed cache identifier — a SHA of (package name, version, resolved dependency set). Per-machine, per-version. The equivalent path does not exist on any other machine, and will not exist on this machine once:

  • codealmanac publishes a new version and npx codealmanac is run again (new hash, new directory, old hook path still points at the old cache entry);
  • npm GCs old npx cache entries (happens automatically on some npm versions, or on npm cache clean);
  • the user runs rm -rf ~/.npm/_npx/* to reclaim disk space.

In every one of those cases the hook script file disappears and Claude Code invokes a nonexistent command at session end. By design, SessionEnd failures don't surface to the user, so the wiki silently stops being captured. The user's evidence that the hook did fire in the current session is ~/.almanac/.capture-e09d8ede-…jsonl (1.2MB, today) — but that only proves it worked once, while the cache entry still existed.

Evidence 3 — the hook script itself knows about this#

hooks/almanac-capture.sh:38-40:

# Prefer `almanac` on PATH; fall back to `npx codealmanac` if the
# binary isn't linked (happens with non-global installs).
if command -v almanac >/dev/null 2>&1; then
  CMD="almanac"

The script is aware that almanac may not be on PATH and falls back to npx codealmanac. But this fallback only helps if the shell script itself is reachable. The fallback lives inside the script; the path to the script is the unprotected part. The outer invocation is the single point of failure, and the setup wizard puts that single point of failure on an ephemeral cache directory.

Evidence 4 — npm bin discovery is stale#

Attempting to run npm bin -g during this audit returned:

Unknown command: "bin"

npm bin was removed in npm 9. Any install documentation or script that relies on it is broken against modern Node distributions. This isn't the root cause of the user's issue but indicates the install tooling hasn't been tested against current npm in some time.

Evidence 5 — the wiki is fine#

To rule out wiki-level corruption: .almanac/ in the user's project contains:

  • 12 markdown pages in pages/ (aws-s3, doppler, electron-gui, mcp-server, meilisearch, observability, quill-agent, redis, ref-token, research-pipeline, supabase, wiki-data-model)
  • topics.yaml (topic DAG)
  • index.db (SQLite FTS index, 139KB, fresh)
  • README.md (the notability bar + conventions)
  • .bootstrap-20260416-131328.log (1.8MB) — from a different user (cwd: /Users/rohan/Desktop/Projects/openalmanac). This is fine and expected: a collaborator scaffolded the wiki on their machine and committed it; every subsequent clone auto-registers on first query. This is the intended cross-user workflow, not a bug.

The wiki content is healthy. The problem is purely that this user cannot query it from the shell because almanac is not on PATH.


Root causes#

Root cause 1 — documentation diverges from behavior#

setup.ts is documented (in its own docstring, lines 21–44) as doing hook install, guide install, and CLAUDE.md import. Those are the three steps it performs. The README, however, positions bare npx codealmanac as the canonical install invocation and claims it "installs globally." Either:

  • setup.ts should do a global install (or warn loudly that it didn't), or
  • the README should tell users explicitly that npm i -g codealmanac is the install step and codealmanac is only the post-install configurator.

Currently the README makes the user believe the first thing happened. It didn't.

Root cause 2 — encoding install-path-at-time-of-run into user config#

hook.ts resolves the bundled hooks/almanac-capture.sh relative to the running codealmanac module and writes that absolute path into ~/.claude/settings.json. When codealmanac is running from ~/.npm/_npx/<sha>/…, that's the path it writes. The written config is therefore only valid for the exact package directory that was running at configuration time.

The cleaner primitive is: copy the hook script into a stable location under the user's own config tree — for example ~/.claude/hooks/codealmanac-capture.sh — and write that path into settings.json. Then the hook survives version bumps, cache GC, and uninstall/reinstall cycles. This is what hook.ts does for guide files (they get copied to ~/.claude/codealmanac.md and ~/.claude/codealmanac-reference.md); the same pattern should extend to the hook script itself.

Root cause 3 — inverted side-effect asymmetry#

The setup wizard happily modifies user-global, user-visible configuration:

  • edits ~/.claude/settings.json to register a hook;
  • copies mini.md to ~/.claude/codealmanac.md;
  • copies reference.md to ~/.claude/codealmanac-reference.md;
  • appends @~/.claude/codealmanac.md to ~/.claude/CLAUDE.md.

All of these are persistent, span every Claude Code project the user has, and touch files the user might have their own edits to. They are the right side-effects for this tool — but they are also the ones with the highest blast radius if wrong.

The one thing setup won't do — put almanac on PATH — is (a) locally reversible (npm uninstall -g codealmanac), (b) the specific side-effect the user unambiguously wants from running an "install" command, and (c) the thing whose absence leaves the user confused. The priority ordering is backwards: the high-blast-radius actions are automated, the low-blast-radius action is skipped.

Root cause 4 — "branded TUI" prioritized over mechanics#

setup.ts lines 97–110 define an 11-letter ASCII banner with a six-stop grey gradient. The file has more code dedicated to banner rendering and ANSI color helpers than to validating that the resulting install is functional. Doctor does have a detectInstallPath() primitive (doctor.ts:799–827) that walks up from import.meta.url looking for package.json with name === "codealmanac" — the right tool to detect "I am running from an ephemeral npx cache." Setup does not use it. If it did, it could print:

! codealmanac is running from an npx cache directory
  (~/.npm/_npx/1123f5c16135f0b0/node_modules/codealmanac).
  The `almanac` binary will not be on your PATH after this completes.

  To put it on PATH:   npm install -g codealmanac
  Or continue anyway:  the hook will fall back to `npx codealmanac`
                       at session end, but this is fragile — the cache
                       path may be GC'd between invocations.

That is five lines of code and it closes the entire failure class.


Impact#

On a fresh machine, a user running npx codealmanac ends up with:

Resource State Persistent?
~/.claude/settings.json SessionEnd hook Registered, pointing at ephemeral cache path Yes (until cache evicted)
~/.claude/codealmanac.md Present Yes
~/.claude/codealmanac-reference.md Present Yes
~/.claude/CLAUDE.md Modified to import the guide Yes
almanac on PATH Not present N/A
codealmanac on PATH Not present N/A

The user cannot run the documented query commands (almanac search, almanac show, almanac doctor) from their shell. Capture may continue to work for some period — until one of the cache-invalidation events in Evidence 2 fires — at which point it silently stops and the user has no indication until they inspect their sessions and notice no .almanac/.capture-*.jsonl files appearing.

The failure is silent, time-delayed, and invisible to the tool's own diagnostics unless the user specifically runs almanac doctor — which they can't run, because almanac is not on PATH.


Recommendations#

For the codealmanac maintainer#

In rough priority order:

  1. Fix the README, today. Either delete the line that says npx codealmanac "installs globally," or change the wizard to actually do that when invoked under npx. The current divergence is the single highest-leverage bug because it mis-sets user expectations before any code runs.

  2. Copy the hook script into a stable location on install. Write ~/.claude/hooks/codealmanac-capture.sh (or equivalent) during setup and point settings.json at that copy. Follows the existing pattern for guide files. Removes the ephemeral-cache failure mode entirely.

  3. Detect ephemeral-install in setup and warn. Reuse detectInstallPath() from doctor.ts. If the resolved install path is under /.npm/_npx/, /.pnpm-store/, a Yarn cache, or similar, print a prominent warning with the npm i -g codealmanac fix. Exit zero — don't block — but make sure the user sees it.

  4. Have codealmanac (bare) self-install on first run. If the current process isn't reachable via PATH, prompt: "Install codealmanac globally so you can run almanac from your shell? [Y/n]". On yes, spawn npm install -g codealmanac. This is what users think npx codealmanac does and what the README already claims.

  5. Fix npm bin-era tooling. Replace npm bin -g with npm prefix -g + /bin construction, which works on npm 9+.

  6. Add a smoke test for the install surface. A CI job that runs npx codealmanac --yes in a clean container, then asserts which almanac succeeds, scheduled auto-capture uses a durable command path, and almanac doctor reports no errors. The current failure mode survived to a shipping release because nothing tests the post-install invariant.

For a user stuck in this state (the reader of this audit)#

Execute in order:

npm install -g codealmanac
which almanac                               # should resolve under your npm prefix
almanac automation install                  # installs scheduled capture from the durable command path
almanac doctor                              # reports binary location + automation + guides + import line + wiki stats

After almanac automation install, inspect almanac automation status or almanac doctor --install-only and verify the scheduled command does not point under ~/.npm/_npx/<sha>/.... Any query command (almanac search --mentions <path>, almanac show <slug>) should now work.


Appendix A — what's actually good#

Flagging this so the audit isn't purely negative:

  • detectInstallPath in doctor.ts:799 is the right primitive for self-location. It just isn't called from the place that needs it most (setup).
  • The hook script's fallback (command -v almanac || npx codealmanac) is defensive in the right direction. It just can't protect against its own path disappearing.
  • Wiki auto-registration on first query handles the cross-user committed-.almanac/ case gracefully. The fact that rohan's bootstrap log is intact in the user's repo and the wiki data is queryable once the CLI is installed shows this works.
  • Hook script itself is careful: JSON payload parsing, walks up looking for .almanac/, backgrounds capture, exits zero on all paths. Zero complaints.
  • almanac doctor's install-probe design (walk import.meta.url → find package.json with matching name) is correct and portable. Works for global, npx, local, and dev-from-source installs. Good primitive.

The install surface problems are concentrated in roughly three files (setup.ts, hook.ts, README.md) and are all fixable without touching the wiki data model, the query commands, or the agent pipeline. The core of the product is sound; the onboarding is where it falls down.