Troubleshooting
When Pigeon stops behaving — briefMe returns nothing, the MCP server won’t connect, the Costs page is empty, the launchd service is silent — start here.
First-line debug: npm run doctor
Section titled “First-line debug: npm run doctor”Before anything else, run:
npm run doctorThis is the eight-check install-health diagnostic. It catches the failure modes documented below — legacy MCP key shape, hook drift, launchd label drift, missing .mcp.json on connected repos, server-vs-package version skew, missing tracker.md, WAL-file phantom-drop pressure, and FTS5 half-state — and prints a copy-pasteable fix for every failure.
Pigeon Doctor — install health check────────────────────────────────────✓ MCP registration PASS✓ Hook drift PASS✓ launchd label PASS✓ Connected repos PASS✓ Server version PASS✓ Per-project tracker.md PASS✓ WAL hygiene PASS✓ FTS5 sanity PASS
8 passAll checks passed.Exit code is 0 when everything passes (warnings included), 1 on any fail. If the doctor identifies the problem and prints a fix, you’re done — apply it and re-run.
If the doctor passes but Pigeon still misbehaves, find the symptom below.
Common failure modes
Section titled “Common failure modes”MCP server shows “failed” in Claude Code
Section titled “MCP server shows “failed” in Claude Code”Symptom. Your agent says the pigeon MCP server failed to start, or getTools / briefMe aren’t available.
Diagnose.
npm run doctor— look at the MCP registration check. It identifies four states: pigeon registered (good), both pigeon + legacy registered (warn — remove the legacy key), only legacyproject-trackerregistered (fail — v6.0 dropped this key), or no Claude config found at all.- From inside your project:
/mcpin Claude Code, then tryscripts/pigeon-start.shmanually to see the stderr. - Check
.mcp.jsonexists in the project root:ls -la .mcp.jsonfrom the repo you opened the agent in.
Fix.
- Missing
.mcp.json. Run/path/to/pigeon/scripts/connect.shfrom inside the project root. This writes.mcp.jsonand installs the slash commands. - Legacy
project-trackerkey. Open~/.claude.json(or~/.claude-alt/.claude.json), renamemcpServers.project-tracker→mcpServers.pigeon, and swapscripts/mcp-start.sh→scripts/pigeon-start.shin the command path. Thennpm run migrate-rebrandto clean up any other stale references. - Restart the agent. MCP servers load at session start —
.mcp.jsonedits do not hot-reload. Quit and reopen Claude Code (or whichever agent) after any config change.
briefMe fails on missing repoPath
Section titled “briefMe fails on missing repoPath”Symptom. briefMe returns an error, a “needsRegistration” branch, or _versionMismatch warnings instead of a session primer.
This is the most common cause of briefMe failing in practice — the project’s repoPath was never bound, or got unbound, so the tool can’t auto-resolve which project owns the current working directory.
Diagnose.
npm run doctor— look at the Connected repos check. It iterates every project with a registeredrepoPathand verifies.mcp.jsonis present and uses the new key shape.- Ask the agent to run
checkOnboarding. If it returnsneedsRegistration: true, the cwd doesn’t match any project’srepoPath.
Fix.
- From inside the project, ask the agent to run
registerRepo(or runscripts/connect.shfrom the repo root). The MCP server records the bind, and the nextbriefMecall resolves cleanly from cwd. - If
repoPathis bound but stale (you moved or renamed the directory), clear it via Prisma Studio (npm run db:studio) →Projecttable → setrepoPathto null on the affected row, then re-bind from the new location.
Schema drift / “table not found” / mid-update wedge
Section titled “Schema drift / “table not found” / mid-update wedge”Symptom. Prisma errors after git pull, missing tables, the UI throws on the first query, or service:update aborts complaining about a destructive change.
Diagnose.
- Tail the service logs:
npm run service:logs. Most schema errors print here on first request. - Open Prisma Studio:
npm run db:studio. Eyeball the tables against the columns named inCHANGELOG.mdfor the version you just pulled to. npm run doctor— the WAL hygiene check warns whendata/tracker.db-walis past ~4 MiB. Past that threshold,prisma db pushhas been observed to phantom-drop tables (the v5.0 incident).
Fix.
- Additive schema changes (the common case). Run
npm run service:update— it runsprisma db pushfor you, drops the derived FTS5 index, rebuilds, and restarts the service. The FTS index rebuilds itself lazily on first knowledge-search per project. - Destructive schema changes.
prisma db pushaborts and prints the exact prompt. Runnpx prisma db pushdirectly so it can prompt for data-loss confirmation. Back up first if you’re on a MAJOR bump:Terminal window cp data/tracker.db data/tracker.db.pre-$(node -p "require('./package.json').version") - WAL pressure. Truncate the WAL before re-running the update:
Terminal window sqlite3 data/tracker.db "PRAGMA wal_checkpoint(TRUNCATE);"npm run service:update - Mid-update wedge. Restore the backup and start over:
cp data/tracker.db.pre-X.Y.Z data/tracker.db && npm run service:update. The full sequencing rules — including running migration scripts in CHANGELOG order — are in docs/UPDATING.md.
FTS5 search returns nothing / half-state
Section titled “FTS5 search returns nothing / half-state”Symptom. Knowledge search comes back empty even though you know cards / comments / notes exist with the search terms.
Diagnose. npm run doctor — the FTS5 sanity check. Three failure shapes:
knowledge_ftsexists, shadow tables missing → fail (corrupt half-state, doctor’s fix is to drop and let the server recreate).knowledge_ftsmissing, shadow tables present → fail (orphaned shadows from a previous phantom-drop).- Neither parent nor shadows present → warn (will be created on next MCP server start).
Fix.
- Half-state with orphan shadows. The doctor’s
fixline names the exactsqlite3command. After dropping, restart the service soinitFts5recreates the virtual table. - Lazy rebuild. The first knowledge search per project triggers a per-project rebuild from source rows (cards, comments, claims, notes, repo markdown). No manual rebuild step is needed once the table exists.
macOS launchd service is silent / wrong label
Section titled “macOS launchd service is silent / wrong label”Symptom. Nothing on localhost:3100, npm run service:status shows the service isn’t loaded, or two copies are running and fighting for the port.
Diagnose.
npm run service:status— the one-liner.npm run doctor— the launchd label check. It catches the v5 → v6 rebrand foot-gun where the legacycom.2nspired.project-trackerjob is still loaded but the newcom.2nspired.pigeonjob never installed.npm run service:logs— tails stdout/stderr from the launchd-managed process. The first line of any startup error lives here.
Fix.
- Service never installed.
npm run service:install. - Legacy job still loaded. Doctor prints the exact
launchctl bootoutcommand. Run it, delete the old.plist, thennpm run service:install. The full command:Terminal window launchctl bootout gui/$(id -u)/com.2nspired.project-tracker \&& rm -f ~/Library/LaunchAgents/com.2nspired.project-tracker.plist \&& npm run service:install - Service is loaded but the build is stale.
npm run service:updaterebuilds and restarts. - Code changed but the service didn’t update.
npm run service:updateafter everygit pullwhen running the service. If you forget, the Server version doctor check flags the mismatch (running v6.0.5 vs. package.json v6.1.0 → fail withnpm run service:updateas the fix).
Stop hook silently no-ops (token tracking)
Section titled “Stop hook silently no-ops (token tracking)”Symptom. The Costs page renders empty, or the token-tracking setup dialog says “RECORDING” but getProjectSummary returns $0. You added the hook to your Claude config but events aren’t landing.
This one is silent by design — the hook fires, can’t find what it’s looking for, and drops the call without an error in the agent’s UI. There are three known traps.
Diagnose.
- Wrong file. Claude Code 2.1.x reads hooks from
settings.jsononly. Thehookskey in.claude.jsonis silently ignored. Move the snippet into one of these (in precedence order):<repo>/.claude/settings.local.json,<repo>/.claude/settings.json,~/.claude/settings.json,~/.claude-alt/settings.json,$CLAUDE_CONFIG_DIR/settings.json. - Wrong type. Use
type: "command"(runningscripts/stop-hook.shas a subprocess).type: "mcp_tool"no-ops without error in CC 2.1.x for this hook config. - Stale
serverfield on the hook entry. If amcp_toolhook still references"server": "project-tracker", it silently no-ops post-rename. The doctor’s Hook drift check finds these. - Script not executable.
chmod +x scripts/stop-hook.sh. - Diagnostic log. The hook writes a line to
<repo>/data/stop-hook.logon every fire (success or fail).tail -f data/stop-hook.logwhile you trigger a Stop event.
Fix. Re-paste the snippet from the in-app Token tracking setup dialog (Costs page → “Set up token tracking”). It embeds your machine’s absolute path, names the right file, and uses type: "command". Click Re-check in the dialog after pasting; the same status pill flips to green when events start landing. Full reference: docs/token-tracking.md.
Old MCP tool name returns “tool not found”
Section titled “Old MCP tool name returns “tool not found””Symptom. A prompt, slash command, hook, or saved transcript references a tool name that the v6+ server rejects with tool not found.
Diagnose. This is almost always a pre-v6 tool name surviving in someone’s prompt or hook config. The most common offender is endSession (renamed saveHandoff in v5.2, alias removed in v6.0).
Fix. Look the old name up in docs/MIGRATION-HISTORY.md — it covers every rename, consolidation, and removal across pre-v6 history with the new canonical name. Update the prompt or hook, restart the agent.
briefMe shows _versionMismatch
Section titled “briefMe shows _versionMismatch”Symptom. briefMe returns a session primer with a _versionMismatch warning attached, even though the doctor passes.
Diagnose. The agent’s MCP client cached the server manifest at session start, but the server has been rebuilt mid-session (typically because npm run service:update ran in another window).
Fix. Restart the agent. The MCP client re-fetches the manifest on reconnect and the warning clears.
”Things were working yesterday” — full reset
Section titled “”Things were working yesterday” — full reset”Symptom. Multiple things broken at once after a long break, or after a major version bump you didn’t notice.
Diagnose.
git log --oneline HEAD..origin/main | head -20 # what landed since you last pulledcat CHANGELOG.md | head -100 # major bumps + migration notesnpm run doctor # current install healthFix. Follow docs/UPDATING.md end-to-end. The short version: back up the DB, git pull, run any migration scripts named in the CHANGELOG in order (don’t skip versions), then npm run service:update. The post-update doctor pass (which service:update runs automatically) will surface anything still wrong via briefMe._upgradeReport on your next session.
When the doctor doesn’t catch it
Section titled “When the doctor doesn’t catch it”If npm run doctor is all green but Pigeon is still misbehaving:
npm run service:logs— tail the launchd service stdout/stderr. Most runtime errors print here.npm run db:studio— eyeball the schema and recent rows.- Check the browser console at localhost:3100 — UI errors with stack traces land there.
- File an issue at github.com/2nspired/pigeon/issues with: the doctor output, the version you’re on (
node -p "require('./package.json').version"), and the relevant log tail.
Read next
Section titled “Read next”- Quickstart — fresh-install path.
- Anti-patterns — the workflow pitfalls (a different category from the install-and-config failures above).
- Updating — what to run after
git pull. - MCP migration history — old tool names that no longer resolve.