Skip to content

CodeNavi in your PR review bot (GitHub Actions / Forgejo Actions)

A CI review bot is a coding agent run headless on a runner: a workflow fires on a pull request, the agent reads the diff, and posts a review. Any bot that can load an MCP server can use CodeNavi as its eyes on the codebase — and the one tool a reviewer actually needs, diff_impact, is built for exactly this job.

The bot reads your diff; your codebase never leaves the box. The index, the embedder, the call graph and the search all stay on the CodeNavi host. What leaves is the diff the runner already has and the answers the bot asks for — not the repository. CI review is usually the moment a team first pipes whole repos to a cloud model. With CodeNavi in the loop, the model asks questions about the code instead of reading it.

This page shows the recipe for Anthropic's claude-code-action on GitHub and its Forgejo-capable fork on a self-hosted forge. The same --mcp-config / --allowedTools shape works for any headless agent that takes Claude Code CLI arguments.

What the bot gets

Tool Why a reviewer wants it
diff_impact Paste the PR's unified diff → every caller of every symbol the diff touches, test files flagged separately. Blast radius before merge; no competitor's bot has this.
impact_analysis Blast radius of one named symbol (when the diff touches a hub).
find_callers Who calls this function — reverse call-graph lookup.
semantic_search "Is there already a helper for this?" — concept search, not grep.
read_file Read the flagged files (lock-free, safe to batch).

Keep the allowlist to these five plus a read-only git diff. A review bot has no business reindexing or writing notes; the defaults below give it none of that.

Prerequisites

  1. A running CodeNavi with the PR's repository indexed (list_codebases shows its name — a single-codebase install is usually default).
  2. CODENAVI_AUTH_TOKEN set in the server's .env — never run an unauthenticated endpoint that a runner can reach.
  3. The runner can reach the CodeNavi host: LAN, tailnet, or a tunnel — see remote-access.md. GitHub-hosted runners cannot see your LAN; use a self-hosted runner or a tunnel. Forgejo runners are self-hosted by definition.
  4. Two repository (or org) secrets:
  5. CODENAVI_URL — e.g. http://codenavi.internal:8765/mcp/myrepo (scoped: every search is pinned to that codebase, whatever the prompt asks for)
  6. CODENAVI_AUTH_TOKEN — the bearer token from the server's .env
  7. ANTHROPIC_API_KEY (or the Bedrock / Vertex / Foundry equivalent the action supports).

The MCP config

Both workflows below pass the same JSON to Claude Code's --mcp-config:

{
  "mcpServers": {
    "codenavi": {
      "type": "http",
      "url": "${{ secrets.CODENAVI_URL }}",
      "headers": { "Authorization": "Bearer ${{ secrets.CODENAVI_AUTH_TOKEN }}" }
    }
  }
}

The server name codenavi is what the tool names hang off: Claude Code exposes MCP tools as mcp__<server>__<tool>, so the allowlist names mcp__codenavi__diff_impact and so on. Rename the server and the allowlist must follow.

GitHub Actions

.github/workflows/codenavi-review.yml:

name: CodeNavi PR review
on:
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  review:
    runs-on: self-hosted        # must reach the CodeNavi host; see Prerequisites
    permissions:
      contents: read
      pull-requests: write
      issues: write
      id-token: write
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0        # full history so `git diff` against the base works

      - uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          prompt: |
            Review this pull request using CodeNavi.
            0. Call mcp__codenavi__reveal_tools with group="relations" first — the
               server's default lean profile hides diff_impact, find_callers and
               impact_analysis until revealed.
            1. Run `git diff origin/${{ github.base_ref }}...HEAD` and pass the full
               output to mcp__codenavi__diff_impact (depth 2).
            2. For every non-test file it flags, read_file it and check whether the
               change breaks the caller. Use find_callers / impact_analysis on any
               symbol marked ⚠ Hub.
            3. Use semantic_search once to check whether the diff re-implements
               something that already exists.
            4. Post one review comment: blast radius (files + callers), concrete
               breakages with file:line, and a merge/hold recommendation.
            Keep one or two CodeNavi searches in flight, never a burst: they serialize on the GPU.
          claude_args: |
            --mcp-config '{"mcpServers":{"codenavi":{"type":"http","url":"${{ secrets.CODENAVI_URL }}","headers":{"Authorization":"Bearer ${{ secrets.CODENAVI_AUTH_TOKEN }}"}}}}'
            --allowedTools "Bash(git diff:*),mcp__codenavi__reveal_tools,mcp__codenavi__diff_impact,mcp__codenavi__impact_analysis,mcp__codenavi__find_callers,mcp__codenavi__semantic_search,mcp__codenavi__read_file"
            --max-turns 25

Forgejo Actions (self-hosted forge)

Anthropic's action is GitHub-only. The vtmocanu/claude-code-action fork adds native Forgejo support (REST-only, dedicated bot user, platform auto-detection). Two Forgejo-specific facts shape the workflow:

  • Forgejo runners pre-fetch every uses: reference before evaluating if:, and resolve short owner/repo@v1 refs against the instance's default actions source (code.forgejo.org by default, which mirrors actions/checkout but not this fork). Mirror the action into your forge and reference it by full URL.
  • There is no claude[bot]: create a bot user (claude), give it a repo + user:read token, and pass its identity explicitly. The fork's docs/forgejo.md walks through it.

.forgejo/workflows/codenavi-review.yml:

name: CodeNavi PR review
on:
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  review:
    runs-on: ubuntu-latest      # a label your registered runner carries
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: https://forge.example.com/actions/claude-code-action@v1   # your mirror
        with:
          platform: "forgejo"   # always explicit; the URL heuristic misfires on GHES
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          github_token: ${{ secrets.CLAUDE_BOT_TOKEN }}
          bot_id: "42"          # curl -s https://forge.example.com/api/v1/users/claude | jq .id
          bot_name: "claude"
          bot_email: "[email protected]"
          prompt: |
            Review this pull request using CodeNavi.
            0. Call mcp__codenavi__reveal_tools with group="relations" first — the
               server's default lean profile hides diff_impact, find_callers and
               impact_analysis until revealed.
            1. Run `git diff origin/${{ github.base_ref }}...HEAD` and pass the full
               output to mcp__codenavi__diff_impact (depth 2).
            2. For every non-test file it flags, read_file it and check whether the
               change breaks the caller. Use find_callers / impact_analysis on any
               symbol marked ⚠ Hub.
            3. Use semantic_search once to check whether the diff re-implements
               something that already exists.
            4. Post one review comment: blast radius (files + callers), concrete
               breakages with file:line, and a merge/hold recommendation.
            Keep one or two CodeNavi searches in flight, never a burst: they serialize on the GPU.
          claude_args: |
            --mcp-config '{"mcpServers":{"codenavi":{"type":"http","url":"${{ secrets.CODENAVI_URL }}","headers":{"Authorization":"Bearer ${{ secrets.CODENAVI_AUTH_TOKEN }}"}}}}'
            --allowedTools "Bash(git diff:*),mcp__codenavi__reveal_tools,mcp__codenavi__diff_impact,mcp__codenavi__impact_analysis,mcp__codenavi__find_callers,mcp__codenavi__semantic_search,mcp__codenavi__read_file"
            --max-turns 25

The fork's docs/forgejo.md also lists what Forgejo does not have (no commit signing, no GraphQL, different webhook event names) — none of it affects the CodeNavi part.

The three things that bite people

The default tool profile hides the relations tools. Since 1.3.23 the server advertises an 11-tool core and loads the rest on demand: diff_impact, find_callers and impact_analysis stay hidden until reveal_tools(group="relations") runs, which is why both recipes allow-list it and call it first. If the bot cannot use a freshly revealed tool within the same run, start the server the bot talks to with CODENAVI_TOOL_PROFILE=full instead — the profile is per server, not per endpoint.

  1. diff_impact matches the diff's line numbers against the index. The index must cover the PR's base branch and be fresh. Reindex the codebase (incremental — seconds) on merges to the base branch; a webhook or a cron on the CodeNavi host does it (see webhooks.md). A stale index turns a blast radius into a guess. The result is a lower bound either way — the call graph is name-based and static.
  2. The runner must reach the CodeNavi host, and only the CodeNavi host must be reachable. Scope CODENAVI_URL to /mcp/<codebase> so every search is pinned to the PR's codebase — note that list_codebases still returns every index's name on a scoped endpoint; scoping pins queries, it does not hide the catalog. Keep CODENAVI_AUTH_TOKEN a secret, not a variable, and do not put the reindex or note tools on the allowlist.
  3. One or two searches in flight. Searches serialize on their GPU phases; a burst queues and, past 15 s, returns busy. The prompt says so, and --max-turns keeps a runaway review bounded. Expect a few cents per PR review at these limits.

Verify before wiring the workflow

Run the same thing from a shell on the runner (or any box that can reach the host) — if this works, the workflow will:

export CODENAVI_URL=http://codenavi.internal:8765/mcp/myrepo
export CODENAVI_AUTH_TOKEN=...
claude -p "call list_codebases, then run diff_impact on this diff: $(git diff HEAD~1)" \
  --mcp-config "{\"mcpServers\":{\"codenavi\":{\"type\":\"http\",\"url\":\"$CODENAVI_URL\",\"headers\":{\"Authorization\":\"Bearer $CODENAVI_AUTH_TOKEN\"}}}}" \
  --allowedTools "mcp__codenavi__list_codebases,mcp__codenavi__diff_impact"

Verified run (laravel testbed, one-line hunk inside Str::snake): diff_impact at depth 2 returned 156 affected files — 79 code, 77 test — with the Eloquent relation/foreign-key hubs flagged, and the review correctly noted the hunk was comment-only so the realistic test set was SupportStrTest + SupportStringableTest. Wide radius on a trivial hunk is expected: it is the static lower-bound the reviewer then narrows.

A 401 means the token does not match the server's .env; an Unknown codebase error means the /mcp/<name> scope is wrong — list_codebases on the unscoped /mcp shows the real names. Full status codes: api-reference.md.

Other headless agents

The recipe is not Claude-specific. Any agent that runs headless and loads MCP servers from a config file works the same way — the only thing that changes is the config shape (Codex: ~/.codex/config.toml with bearer_token_env_var; Antigravity: serverUrl + literal token; see connecting-codex.md and connecting-antigravity.md). Give the bot the same five tools and the same prompt.