使用说明
agentic-nix
Hybrid code search for Claude. Index your codebases, documentation, and GitHub issues into PostgreSQL (ParadeDB BM25 + pgvector HNSW). A Rust MCP server exposes the index to Claude Code so it can answer questions about your code without reading every file from scratch.
What this replaces: Claude reading 50 files one by one to find something. Instead, Claude queries the index with a single tool call and gets the 10 most relevant chunks in under a second.
How it works
your repos ──► ingest ──► PostgreSQL (ParadeDB)
│
BM25 + vector Ollama
hybrid search ◄── embeddings
│
MCP server ──► Claude Code
- The
ingestbinary walks your repos, extracts named symbols via tree-sitter, embeds each chunk via Ollama, and stores everything in PostgreSQL. - The
mcp-serverbinary sits between Claude and the database, exposing search tools over the MCP protocol (stdio). - Claude Code connects to the MCP server automatically and calls the tools when it needs to understand code.
Prerequisites
- Nix with flakes enabled
- Git
That's it. PostgreSQL, Ollama, Rust, and all other tools are managed by Nix.
Enable flakes if you haven't already — add to ~/.config/nix/nix.conf:
experimental-features = nix-command flakes
On Ubuntu or other non-NixOS systems?
nix run .#devwon't use your GPU — CUDA library paths don't resolve correctly for Nix-packaged Ollama outside NixOS. See Non-NixOS / Ubuntu setup below for the system-services alternative.
Non-NixOS / Ubuntu setup
On Ubuntu, use system-managed PostgreSQL and Ollama. The Nix dev shell still provides the Rust
toolchain, just, and all build tools — only the services differ.
1. Install PostgreSQL 17 with ParadeDB and pgvector
Add the PostgreSQL apt repo if you haven't already:
sudo apt install -y postgresql-common
sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh
Install pg_search (ParadeDB BM25) and pgvector:
# ParadeDB repo
curl -fsSL https://apt.fury.io/paradedb/gpg.key \
| sudo gpg --dearmor -o /usr/share/keyrings/paradedb.gpg
echo "deb [signed-by=/usr/share/keyrings/paradedb.gpg] https://apt.fury.io/paradedb/ stable main" \
| sudo tee /etc/apt/sources.list.d/paradedb.list
sudo apt update
sudo apt install -y postgresql-17-pg-search postgresql-17-pgvector
Enable pg_search in PostgreSQL — add to /etc/postgresql/17/main/postgresql.conf:
shared_preload_libraries = 'pg_search'
pg_search.enable_telemetry = off
Restart PostgreSQL:
sudo systemctl restart postgresql
Create the database and apply the schema:
sudo -u postgres createdb codebase
sudo -u postgres psql codebase -c \
"CREATE EXTENSION IF NOT EXISTS pg_search; CREATE EXTENSION IF NOT EXISTS vector;"
# Allow your user to connect (adjust if you use password auth instead)
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE codebase TO $USER;"
sudo -u postgres psql codebase -c "GRANT ALL ON SCHEMA public TO $USER;"
# Enter the Nix dev shell, then apply the schema
nix develop
just migrate
2. Install Ollama
The official installer detects CUDA automatically:
curl -fsSL https://ollama.com/install.sh | sh
Pull the embedding model:
ollama pull hf.co/jinaai/jina-code-embeddings-1.5b-GGUF:Q8_0
Ollama runs as a systemd service and starts on boot. Verify GPU usage with ollama ps after
pulling a model — it should show GPU in the processor column.
3. Continue with the normal setup
Skip the nix run .#dev step — your services are already running. Jump straight to
Build the Rust binaries and continue from there. Everything
else (indexing, MCP registration, Claude integration) is identical.
First-time setup
1. Clone and enter the dev shell
git clone <this-repo> ~/agentic-nix
cd ~/agentic-nix
nix develop
The first nix develop downloads Rust, PostgreSQL, Ollama, and all dependencies. This takes
a few minutes once; subsequent shells are instant.
2. Build the Rust binaries
just build
This produces target/release/mcp-server and target/release/ingest. You only need to
rebuild when the Rust source changes.
3. Start the services
In a dedicated terminal (keep it running):
nix run .#dev
This starts:
- PostgreSQL 17 on
localhost:5432withpg_search(BM25) andpgvector(HNSW) loaded. Thecodebasedatabase is created automatically with the full schema. - Ollama on
localhost:11434with thejina-code-embeddings-1.5bmodel pulled. First start pulls the model (~1.5 GB); subsequent starts are instant.
Data is stored under ./data/ relative to where you run the command, so always run it from
the repo root.
4. Verify everything is running
# Should return a connection
just psql
# Should return empty tables (schema applied automatically)
just stats
Indexing your code
Index a codebase
just index /path/to/your/repo
This walks the repo, extracts symbols for TypeScript, JavaScript, Python, Rust, and Haskell files using tree-sitter (functions, classes, structs, etc.), and falls back to overlapping line windows for other file types. Files that haven't changed since the last run are skipped.
Re-index everything from scratch:
just reindex /path/to/your/repo
Index multiple repos — just run the command once per repo:
just index ~/work/frontend
just index ~/work/backend
just index ~/work/infrastructure
Index documentation
Discovers AGENTS.md, CLAUDE.md, README.md, and any .agent/workflows/, .agent/skills/,
.agent/plans/, .agent/SOPs/ markdown files:
just index-docs /path/to/your/repo
Index GitHub issues and pull requests
export GITHUB_TOKEN=ghp_... # optional but recommended (5000 req/hr vs 60)
just index-github anthropics/claude-code
This fetches all issues, PRs, and their comments. Subsequent runs are incremental — only items updated since the last sync are fetched.
Index a specific stream only:
just index-github-issues anthropics/claude-code # issues only
just index-github-prs anthropics/claude-code # PRs only
Check what's indexed
just stats # row counts per table
just sync-status # GitHub watermarks (last sync times)
Connect to Claude Code
The MCP server communicates with Claude Code over stdio. You need to register it once.
Add the MCP server
Run this from the ~/agentic-nix directory:
claude mcp add agentic-nix \
--command "$(pwd)/target/release/mcp-server" \
--env PG_DSN=postgresql://127.0.0.1:5432/codebase \
--env OLLAMA_HOST=http://127.0.0.1:11434
Or add it manually to your Claude Code config (~/.claude.json or project .claude/mcp.json):
{
"mcpServers": {
"agentic-nix": {
"command": "/home/you/agentic-nix/target/release/mcp-server",
"env": {
"PG_DSN": "postgresql://127.0.0.1:5432/codebase",
"OLLAMA_HOST": "http://127.0.0.1:11434"
}
}
}
}
Verify the connection
Start a new Claude Code session and ask:
List all indexed repositories.
Claude should call list_repos and return the repos you've indexed. If it can't connect,
check that the services are running (nix run .#dev) and the binary path is correct.
codex-jail
codex-jail runs Codex with the same Nix, worktree and GitHub proxy
integration as the Claude launcher. The host runs the real GitHub CLI; the
jail's gh is the socket client, with no host GitHub credentials mounted.
nix run .#codex-jail -- --dangerous
nix run .#codex-jail -- --repo owner/another-repo --write
nix run .#codex-jail -- --no-github-auth -- exec "Inspect this project"
GitHub remotes supply the default repository list; --repo OWNER/REPO
adds entries, and --all-repos explicitly removes that restriction.
Without GitHub remotes or explicit repository selection, the proxy is disabled.
--write enables the shared proxy's mutating API/release operations.
--no-github-auth skips the proxy. The packaged client remains installed,
but cannot operate without its socket.
--allow-ssh explicitly forwards the host SSH agent; it is off by default.
--debug reports launcher arguments while redacting environment values and
omitting the Codex prompt. --ro, --rw, and --all-worktrees control
additional mounts. Codex arguments follow a separate --.
The jail uses an isolated global Git config with the host's name/email,
signing disabled, and GitHub SSH remotes rewritten to HTTPS. It does not
import host credential helpers. Private Git-over-HTTPS authentication is
not provided: the shared proxy blocks gh auth, so it cannot serve as
a Git credential helper. Explicit SSH URLs can use --allow-ssh.
The shared proxy's existing policy is unchanged by the Codex integration.
Launcher regression tests use the real proxy server and client with fixture executables for bubblewrap and upstream GitHub access:
nix develop --command cargo test -p agentix-jails --bin codex-jail --test codex_jail
nix build .#codex-jail
claude-jail
claude-jail runs Claude Code inside a bubblewrap
sandbox, giving it read-write access to your project while blocking access to the rest of your
home directory, SSH keys, and host credentials.
Usage
nix run .#claude-jail [options]
Flags for claude itself go after --:
nix run .#claude-jail -- --dangerously-skip-permissions
nix run .#claude-jail --dangerous # shorthand for the above
Flags
| Flag | Description |
|---|---|
--dangerous |
Pass --dangerously-skip-permissions to Claude |
--write |
Allow mutating GitHub operations via the gh proxy (releases, api POST/PATCH/DELETE) |
--repo OWNER/REPO |
Add an extra GitHub repo the proxy may access. Repeatable. |
--no-github-auth |
Skip the gh proxy; no gh available inside the jail |
--allow-ssh |
Forward the host SSH agent socket into the jail. Warning: Claude can sign arbitrary SSH operations with your keys. Never combine with --dangerous. |
--ro PATH |
Bind an extra path read-only at its real path. Repeatable. |
--rw PATH |
Bind an extra path read-write at its real path. Repeatable. |
--debug |
Print every host command and the full bwrap argument list before launching |
What gets mounted
Network access is shared with the host. Everything else is constructed from scratch:
| Path inside jail | Source | Access |
|---|---|---|
/nix |
host | read-only |
/proc, /dev, /tmp |
synthetic | — |
~/bin |
$CLAUDE_JAIL_BIN_DIR |
read-only |
~/.claude |
host ~/.claude |
read-write |
~/.claude.json |
host ~/.claude.json |
read-write |
~/.ssh/known_hosts |
host | read-only |
| SSH agent socket | host (--allow-ssh only) |
read-write |
/tmp/gitconfig |
synthetic (host identity) | read-only |
| project worktree | host worktree root | read-write |
| bare git common dir | host .bare/ or .git/ |
read-write |
| gh proxy socket dir | host tmpdir | read-write |
/etc/{ssl,nix,resolv.conf,…} |
host | read-only |
Git worktree detection. The jail automatically mounts the full worktree root, not just the
current subdirectory. For bare+worktree layouts (e.g. git worktree add), the common git dir is
mounted separately so commits and refs work correctly. The hooks/ directory is masked with a
tmpfs so Claude cannot plant code that runs on the host.
Synthetic gitconfig. A minimal gitconfig is generated from the host's git config --global user.name/email. It sets gpgsign = false, rewrites git@github.com: URLs to HTTPS, and
registers gh auth git-credential as the credential helper. Your real gitconfig is not visible
inside the jail.
Tool set
| Tool | Notes |
|---|---|
claude |
Claude Code |
nix, git |
standard |
gh |
proxied — see below |
curl, bash, python3, direnv |
standard |
coreutils, findutils, jq |
standard |
grep (GNU), ripgrep, sed (GNU) |
standard |
ssh, ssh-keyscan, scp |
openssh client; agent available only with --allow-ssh |
ingest, mcp-server |
from this repo |
GitHub access (gh proxy)
By default, claude-jail starts a gh-jail-server process on the host before entering the
sandbox. A thin gh-jail-client binary, installed as gh inside the jail, forwards every gh
invocation to the server over a Unix domain socket. The server runs the real gh with the host's
existing authentication — no token is ever passed into the jail.
Policy enforced by the server:
| Operation | Read-only (default) | With --write |
|---|---|---|
gh issue, gh pr, gh run, gh workflow |
allowed | allowed |
gh repo view / list / clone / sync |
allowed | allowed |
gh repo create / delete / rename / … |
blocked | blocked |
gh release view / list / download |
allowed | allowed |
gh release create / upload / delete / edit |
blocked | allowed |
gh api — GET |
allowed | allowed |
gh api — POST / PATCH / DELETE |
blocked | allowed |
gh auth, gh config, gh ssh-key, gh gpg-key, gh extension, gh alias |
blocked | blocked |
Repo restriction. The cwd's GitHub remote is allowed automatically. Pass --repo OWNER/REPO
(repeatable) to add more. Any gh call with an explicit -R/--repo flag or an
/api/repos/OWNER/REPO/… path not in the allowed list is rejected by the server.
If the server fails to start, gh is simply unavailable inside the jail (a warning is printed;
the jail still launches). Pass --no-github-auth to skip the proxy entirely.
Security model
| Concern | Mitigation |
|---|---|
| Host filesystem | tmpfs home; only explicit bind mounts visible |
| SSH private keys | not mounted; agent socket excluded by default (--allow-ssh opts in with a warning) |
| Git hooks | hooks/ masked with tmpfs; Claude cannot plant host-side hooks |
| Git identity | synthetic gitconfig; real ~/.gitconfig not visible |
| GitHub credentials | not injected; all gh calls go through the policy-checking proxy |
| Nix daemon | socket forwarded so nix build / nix develop work inside the jail |
Troubleshooting
gh: GH_PROXY_SOCKET not set
The server didn't start (or --no-github-auth was passed). Check the startup output for a
warning line from claude-jail.
gh: cannot connect to proxy at …
The server crashed after creating the socket. Run with --debug to see the full server command
and reproduce the error outside the jail.
gh-jail-server did not create socket within 5 s
The server binary is missing or CLAUDE_JAIL_GH_SERVER is wrong. This env var is set
automatically by the Nix wrapper; when running the raw binary, set it manually.
Commits fail with GPG errors
The synthetic gitconfig sets gpgsign = false. If a system-level gitconfig overrides it, check
GIT_CONFIG_SYSTEM and ensure it isn't re-enabling signing.
Claude can't see files outside the project
Only the worktree root and explicitly bound paths are visible. Use --ro /path or --rw /path
to expose additional directories.
direnv environment not applied
claude-jail runs direnv export json on the host before entering bwrap. If the .envrc isn't
trusted, run direnv allow in the project directory first.
Using the index with Claude
How Claude uses the tools automatically
Once connected, Claude will automatically call the search tools when it needs to understand your code. You don't have to do anything special — just work normally. For example:
- "How does the authentication middleware work?" → Claude searches for auth-related code
- "Why was this API endpoint added?" → Claude searches GitHub issues for context
- "What does the
UserServiceclass do?" → Claude fetches chunks for that symbol
Prompts that get the most out of the index
Being explicit helps Claude know to search rather than guess:
Search the codebase for how we handle database connection pooling.
Look through the indexed GitHub issues for any discussion of rate limiting.
Find all the Rust functions related to embedding and explain how they fit together.
Search the docs for our deployment workflow.
Available tools
| Tool | When to use it |
|---|---|
search_code |
Natural language or code questions about the codebase |
bm25_search |
Exact identifier or symbol lookups (faster, no embeddings) |
search_docs |
Questions about workflows, SOPs, or agent instructions |
search_github |
"Was there an issue about X?" or "How was Y implemented?" |
list_repos |
See what's indexed |
get_file |
Read a complete file by path |
You can guide Claude toward a specific tool:
Use bm25_search to find every place we call `send_email`.
Search GitHub PRs for anything related to the login refactor.
Search only Haskell files for the `parseConfig` function.
Filters
The search tools accept optional filters you can mention in your prompt:
- Language:
search in Rust files,TypeScript only - Symbol kind:
only functions,find all classes,interfaces only - Doc kind:
search workflows,look in SOPs - GitHub:
open issues only,only PRs,in the anthropics/claude-code repo
Keeping the index current
After pulling new code
Re-index is incremental — only changed files are processed:
just index /path/to/your/repo
Scheduled re-indexing (optional)
Add to your crontab or a systemd timer:
# Re-index at 2am every night
0 2 * * * cd ~/agentic-nix && nix develop --command just index ~/work/myrepo
After large refactors
If you've moved many files around, a full re-index is cleaner:
just reindex /path/to/your/repo
Environment variables
All variables have sensible defaults; override as needed.
| Variable | Default | Description |
|---|---|---|
PG_DSN |
postgresql://127.0.0.1:5432/codebase |
PostgreSQL connection string |
OLLAMA_HOST |
http://127.0.0.1:11434 |
Ollama API base URL |
EMBED_MODEL |
hf.co/jinaai/jina-code-embeddings-1.5b-GGUF:Q8_0 |
Embedding model |
RERANK_MODEL |
(empty — disabled) | Set to a fastembed cross-encoder to enable reranking |
GITHUB_TOKEN |
(empty) | GitHub personal access token (raises rate limit to 5000/hr) |
Quick reference
# Services
nix run .#dev # start PostgreSQL + Ollama
# Binaries
just build # build mcp-server + ingest
# Indexing
just index /path/to/repo # code (incremental)
just reindex /path/to/repo # code (force full re-index)
just index-docs /path/to/repo # markdown docs
just index-github OWNER/REPO # GitHub issues + PRs
just reindex-github OWNER/REPO # GitHub (ignore watermarks)
# Inspection
just stats # row counts
just sync-status # GitHub watermarks
just psql # open a psql session
# Schema
just migrate # apply schema.sql (safe to re-run)
Troubleshooting
"Embedding failed (is Ollama running?)"
The MCP server can't reach Ollama. Make sure nix run .#dev is running, or check that
OLLAMA_HOST points to the right address.
"Database error: connection refused"
PostgreSQL isn't running. Start it with nix run .#dev. Data lives in ./data/pg/ —
you must run from the repo root so the path resolves correctly.
Claude doesn't call the search tools Claude doesn't always reach for MCP tools unprompted. Be explicit: "Search the indexed codebase for..." or "Use the search tools to find...".
Slow first query after starting The embedding model loads lazily on the first request. Subsequent queries are fast.
GitHub rate limit hit
Set GITHUB_TOKEN with a personal access token. The unauth limit is 60 requests/hour;
authenticated is 5000/hour.
Ollama uses CPU instead of GPU on Ubuntu
Nix-packaged Ollama can't resolve CUDA libraries outside NixOS. Use the system Ollama installer
instead — see Non-NixOS / Ubuntu setup. Confirm GPU is active with
ollama ps while a model is loaded.