Configuration - Terrio-cz/CodeLoupe GitHub Wiki

Everything has a default; a daemon needs no configuration to answer. Settings live in <home>/config.json (read by the daemon), in environment variables of the daemon, and, per repository, in a .codeloupe.json in its main worktree (baseBranch, taskPattern and the write restrictions below; a repository's file can add write restrictions but never enable writing).

Repositories

codeloupe repos add <folder>... writes the folders (each must hold a .git) to config.json workspaces.repos without touching the rest of the file (a file that is not valid JSON is refused, not rewritten) and asks the daemon a first question per repository so that it is known and its base index starts building; repos list prints what is configured, --json gives the report the desktop app's first-run onboarding reads. The running daemon already knows a repository after that first question; the configuration keeps it across restarts.

Settings

Default Override
State and indexes %LOCALAPPDATA%\codeloupe, ~/Library/Caches/codeloupe, $XDG_CACHE_HOME/codeloupe CODELOUPE_HOME. On Linux and macOS ~ is $HOME when that names a directory (an isolated profile, sudo -E), as for the hook script, the launcher and git, not the passwd entry the JVM would take; the hook script and the daemon always agree on it.
Port 47391 CODELOUPE_PORT or <home>/config.json { "port": … }. A daemon with another CODELOUPE_HOME refuses 47391 and the port configured in the default home: it needs a port of its own (MCP clients find the daemon by port alone).
Default root for tools without root — CODELOUPE_ROOT or config.json defaultRoot
Base branch of a repository origin/HEAD, else origin/main, origin/master, main, master .codeloupe.json { "baseBranch": "origin/master" } in the main worktree
Build worker heap, timeouts 512 MB, query wait 10 s, build 10 min config.json buildHeapMb, queryTimeoutMs, buildTimeoutMs
Reuse of a worktree check 1 s: queries within a second of the last check of their worktree share it config.json overlayCheckMs (1 = check on every query)
Job slots any name, one job each config.json slots { "gradle-test": 2, "vps-test": 1 }
Policy for jobs none (every command allowed) config.json policyHook — argv of a PreToolUse hook, e.g. ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "C:/ws/.claude/hooks/guard.ps1"]; policyTimeoutMs (30 s)
Remote webhook targets none (local only) config.json remoteWebhooks ["https://hooks.example.com"]
Repositories too large to walk more than 40 000 indexed files: a worktree is checked through git alone (changed and untracked files; the stat cache and, if you enabled it, core.fsmonitor and core.untrackedCache make that fast) config.json largeWorktreeFiles
Index reads at once 2 (the rest wait their turn: ten windows asking together would hold ten reads' memory) config.json maxParallelQueries
Parse worker: seconds it lives without a file to parse (0 = the daemon parses in its own process, with an 80 MB heap instead of 64) 300 config.json { "parseWorkerIdleSeconds": 300 }
Budgets that make /status warn p95Ms 1000, queueWaitMs 30000, rssMb 250, busyRate 0.1 config.json budgets { "rssMb": 200 }
Weighted-token budgets of a day and of one agent run (events for the desktop app) none config.json budgets { "dailyWeighted": 150000000, "runWeighted": 20000000 }
One turn for several independent reads (batch) off: the tool adds about 770 characters (about 150 tokens) to the tool list; skills and the session hint tell models to issue independent reads in parallel either way config.json tools { "batch": true } (restart the daemon; the agent's tool list must name mcp__codeloupe__batch)
Writing by declaration (edit) auto: offered when the gap detector shows the need; no write in .git, secrets, conflicted files config.json write { "mode": "on", "linkedWorktreesOnly": true, "deny": ["**/generated/**"], "gate": { "wholeFileReads": 20, "manualRenames": 3, "windowDays": 30 } }
Hooks of the plugin on; steering in advise mode, large from 150 lines, at most 40 pieces of advice a session, none after 4 unheeded in a row; session start: worktree state, map off (map, budget 1200 tokens); session weight: one line at 150k and 300k tokens of context (weight.warnAt); post-edit check: off (postEdit.enabled, at most 5 lines, 300 ms); account stamp: on (stampAccount, local metadata only) config.json hooks (see Plugin hooks); CODELOUPE_HOOKS=off in Claude Code's environment
Account of a session by its Claude Desktop instance none: by identity, then by config directory <home>/accounts.json claude[].desktopDir, the instance's user-data directory: Accounts
Trackers to mirror none config.json trackers, see Trackers
Tracker sync while clients are active, idle stop every 3 min; stops 10 min after the last tool call config.json trackerSyncMinutes, trackerIdleMinutes
Worktree registry, Docker cleanup, ports see the page config.json workspaces: Workspaces and Docker cleanup
Transcript metrics see the page config.json metrics: Metrics and savings
Secrets: rotation reminder, import roots secrets.rotationDays 90 config.json secrets, envImport: Environment and secrets

Listening, logs and status

The daemon listens on 127.0.0.1 only and refuses requests with a foreign Host, any Origin, or without the x-codeloupe header, and most routes also want the token (below); responses carry Connection: close. Calls are logged (tool, latency, size — no content) to <home>/calls.jsonl, the daemon to <home>/daemon.log. /status adds latency (p50/p95 ms, p95 chars, empty and busy rate of the last 1000 calls, per tool) and budgets (ok and the warnings for what exceeds config.json budgets); /status/history lists RSS, heap and CPU readings taken while the daemon is used (one a minute, the last 240).

Who may call the daemon

Host, Origin and the x-codeloupe header keep a web page out; they do not tell one local process from another. Another user of the machine (RDP, fast user switching, a shared Linux box) could otherwise call run, /jobs or the release routes as you. So the daemon makes a token on its first start, <home>/daemon.token (readable by its owner only, mode set at creation; on Windows the folder's per-user ACL), and every route that acts for you or shows what you did wants it in the header x-codeloupe-token. The file is that header line, so curl -H @<home>/daemon.token -H "x-codeloupe: 1" http://127.0.0.1:47391/jobs presents it without the secret on a command line. It is kept across restarts. To rotate it, write a new value of 32 to 128 letters, digits, - or _ into the file: the daemon notices on the next request and no restart is needed (clients read the file again). The token never appears in /status, the logs or the audit.

Route Needs the token
GET /status no (so that anything can find the daemon); it answers a client's nonce with a proof (below)
GET /env/values its own token (x-codeloupe-env-token)
/mcp, POST /api/<tool> of a read-only tool, POST /hook yes; only with api.strict set to false does a caller with x-codeloupe alone get these
a mutating tool (run, env, update, attach, create, edit, and the MCP job tool) over MCP or /api yes: over MCP the call is answered with the way to get it
everything else: /jobs, /workspaces, /resources, /processes, /reconcile, /ports, /events, /webhooks, /ui-api, /shutdown, /status/history, /session-weight yes

A wrong token is refused wherever it is sent (401), so a client that holds an old one learns it at once. The default is strict: a read-only code query (find, outline, symbol, ...) needs the token as well, because the index shows another user the file and symbol names of your repositories. The price is one step after an update: an MCP entry made before the token existed (--header "x-codeloupe: 1" only) is answered 401 until you replace it with the entry codeloupe mcp-config prints. On a machine that only you use you can instead keep the old entry by setting { "api": { "strict": false } } in config.json (restart the daemon); the read-only tools then answer a caller with x-codeloupe alone, run, env, edit and job still want the token, and /status counts the read-only calls without it (auth.withoutToken).

Clients prove who they talk to before they send it. The CLI, the desktop app and hook.sh put a random nonce in the header x-codeloupe-nonce of GET /status; the daemon answers in x-codeloupe-proof with SHA-256 of codeloupe-proof:<token>:<nonce>, which only a holder of the token can compute. A process that took the port after a crash cannot, and gets neither the token nor a prompt. The CLI also refuses a daemon whose pid is not the one in daemon.json.

What you see after an update:

  • An old daemon still running has no token file; the new CLI, app and hook send only x-codeloupe, as before. Restart it (codeloupe stop, then codeloupe start) to get the token.
  • An old MCP entry (--header "x-codeloupe: 1" only) is answered this needs the daemon token .... Run codeloupe mcp-config and add the entry it prints; its headersHelper (codeloupe mcp-headers) gives Claude Code the token on every connection, without writing it into Claude Code's configuration. The plugin does the same with hooks/mcp-headers.cmd (it runs mcp-headers.sh) once the plugin is updated from the marketplace.
  • An old desktop app against a new daemon gets 401 on its screens; update the app together with the CLI (the installers do).
  • An old hook.sh against a new daemon sends no token and is refused (401): update the plugin, or set api.strict to false.
⚠️ **GitHub.com Fallback** ⚠️