Documentation
Noctra is an autonomous ticket-to-PR agent. Point it at a trigger source — Linear, Jira, or GitHub Issues — and it implements tickets, opens pull requests, and sees them through — answering reviews and fixing CI — while you sleep.
Introduction
Noctra runs as an installable Go CLI. On a timer it polls your configured trigger source — Linear, Jira, or GitHub Issues — for issues, dispatches Claude Code, OpenAI Codex, GitHub Copilot, or Antigravity CLI to implement each one in an isolated git worktree, optionally runs a second-model review, opens a PR with the GitHub CLI, and marks the ticket as handled (moving it to your review state on Linear or Jira, or removing the trigger label and commenting on a GitHub issue). With auto-iterate enabled it then watches those PRs and pushes follow-up commits in response to review feedback and failing CI.
- Self-hosted & open source — runs on your machine or a Raspberry Pi.
- No extra API cost for coding — runs on your Claude Code, ChatGPT (Codex), GitHub Copilot, or Google AI Pro subscription.
- Multi-repo — one instance serves many repos, routed per ticket.
Quick start
You'll need the GitHub CLI (gh, authenticated) and at least one supported agent CLI: Claude Code, OpenAI Codex, GitHub Copilot, or Antigravity CLI. Install Noctra itself with the script, Homebrew on macOS (brew install ahmadAlMezaal/tap/noctra), go install, or a prebuilt binary from GitHub Releases.
# One-liner: install the latest release binary (no Go toolchain) curl -fsSL https://raw.githubusercontent.com/ahmadAlMezaal/noctra/main/scripts/install.sh | sh # macOS (Homebrew): brew install ahmadAlMezaal/tap/noctra # or: go install github.com/ahmadAlMezaal/noctra/cmd/noctra@latest claude # or: codex login / agy — authenticate your agent once gh auth login # GitHub access for PRs noctra setup # guided ~/.noctra/ config (backend, Linear, notifications) noctra doctor # preflight tokens, tools, repos, models noctra # start the poll loop
noctra: command not found? The install script drops the binary in ~/.local/bin — if that's not on your PATH, add it (the installer prints the exact line for your shell) or open a new shell. Then map each Linear project to a repo with a Repo: directive (see multi-repo routing), move a ticket into your trigger state or add the trigger label, and watch it become a PR.
Agent backends
Noctra implements each ticket with the coding agent you choose via AGENT_BACKEND — Claude Code (claude, the default), OpenAI Codex (codex), GitHub Copilot (copilot), or Antigravity CLI (agy). You only need the CLI for the backend you select; noctra doctor checks for the right one. Authenticate it once on the host — claude or codex login using a subscription login (no extra per-token coding cost beyond that subscription), or an API key (ANTHROPIC_API_KEY / OPENAI_API_KEY) for headless Docker/cloud runs (note that API-key auth is billed per token by the provider, separate from any subscription). The Copilot backend uses the standalone copilot CLI (npm i -g @github/copilot, Node 22+) and authenticates through your GitHub Copilot subscription via gh — gh auth login or GH_TOKEN, no separate API key. The Antigravity backend uses Google's agy CLI — install it from antigravity.google and run agy once to log in with your Google AI Pro subscription; no separate API key is needed. Noctra never stores agent credentials — it inherits whatever the selected CLI is logged into.
Configuration
The setup wizard writes a .env under ~/.noctra/ with your backend, Linear key, optional Gemini/notification settings, an optional sweep schedule for automatic cleanup, and optional dashboard settings — or copy .env.example and edit it by hand (see the environment reference). Repos aren't configured here; they're routed per ticket from each Linear project (see multi-repo routing).
The loop
Each tick: poll the configured trigger source (Linear, Jira, or GitHub Issues) → resolve the repo from the ticket or project → create a worktree → dispatch the selected agent backend → optional review gate → commit, push & open the PR → mark the ticket as handled and notify. Tickets run concurrently up to MAX_CONCURRENT, each in its own worktree. If the agent determines a ticket requires no changes, Noctra archives it instead of opening an empty PR.
Trigger modes
Noctra can poll Linear (default), Jira Cloud, GitHub Issues, or any combination. Set TICKET_SOURCES to a comma-separated list of sources to poll (e.g. linear,jira,github).
With Linear, pick up work from a state-based queue (usually the Next column) or from a label-based queue using the noctra label. State mode is useful when Linear workflow state is the source of truth; label mode lets you opt tickets in without moving them between states. After the PR is opened, Noctra moves the ticket to your IN_REVIEW_STATE.
With GitHub Issues, Noctra scans the repos listed in GITHUB_ISSUES_REPOS for open issues carrying the GITHUB_TRIGGER_LABEL label. It implements them the same way — same agent dispatch, same PR workflow, same notifications. After the PR is opened, Noctra removes the trigger label and comments on the issue with a link to the PR (GitHub Issues has no state-column concept, so there is no IN_REVIEW_STATE transition).
With Jira Cloud, Noctra polls the project in JIRA_PROJECT via JQL for issues in the JIRA_TRIGGER_STATUS status (or carrying the JIRA_TRIGGER_LABEL label), authenticating with JIRA_USER_EMAIL + JIRA_API_TOKEN against your JIRA_BASE_URL. Repo routing comes from Repo: / Branch: directives in the issue description, exactly like the others. After the PR opens, the issue transitions to JIRA_IN_REVIEW_STATUS.
Multi-repo routing
The target repo is chosen per ticket from its Linear project — not a single global path — so one instance serves many repos with no config edits to switch between them. Repos are cloned on demand into ~/.noctra-repos/ and lock-guarded against concurrent clones.
The project Repo: directive
Add a Repo: line to the Linear project's description and every ticket in that project routes to that repo — no config file, no wizard, no redeploy; it lives entirely in Linear. It accepts owner/name (expanded to a GitHub HTTPS URL) or a full https:// / git@ git URL verbatim — so SSH, GitLab, and other non-GitHub hosts work too. An optional Branch: overrides the default branch (otherwise auto-detected from origin/HEAD).
# In the Linear project's description: Repo: your-org/your-repo Branch: main # optional — defaults to the repo's default branch
Resolution order at dispatch — the first match wins: (1) the project Repo: directive, (2) REPO_PATH from .env as a single-repo fallback if set. With neither, the ticket is skipped with a Linear comment. The host needs git access to each repo — an SSH key, or gh auth login / GH_TOKEN for HTTPS — and Noctra checks access with git ls-remote before cloning.
Worktrees & logs
Each ticket gets an isolated worktree at ~/.noctra-worktrees/<IDENTIFIER> on a noctra/<id> branch, so parallel tickets never share a working directory. Per-ticket logs live at .agent-logs/<IDENTIFIER>.log and append across attempts.
Agent Teams
Claude-only. Set USE_AGENT_TEAMS=true and a lead Claude agent delegates implementation, tests, and review to teammates running in parallel — stronger on complex tickets, at the cost of more tokens per run. Requires a recent claude CLI; it doesn't apply to the Codex or Copilot backends. Off by default, where each ticket runs as a single agent session — fast, cheap, and light enough for a Raspberry Pi.
Dashboard
Set DASHBOARD_ADDR (e.g., :8080) and DASHBOARD_TOKEN to enable a built-in web dashboard — a Preact-powered, night-shift observatory UI built around dark tones and live telemetry. It shows active runs, queued tickets with retry counts, skipped tickets, and budget/usage stats — streamed live over SSE so the page updates in real time without polling. Each active run exposes a log tail you can follow directly in the browser; logs redact secrets (API keys, tokens, and other sensitive values) automatically so they are safe to view on screen. If only a port is given, Noctra binds to 127.0.0.1 automatically; the server refuses to start without a token.
Authentication is two-tier: DASHBOARD_TOKEN grants viewer access (read-only monitoring), while DASHBOARD_ADMIN_TOKEN grants operator access — everything a viewer sees, plus controls to kill, requeue, pause, and retry runs directly from the UI. Authenticate with a Bearer header or a ?token= query parameter for browser access. noctra setup prompts for both values during first-time configuration.
Auto-iterate: PR review feedback
Set AUTO_ITERATE_PRS=true and Noctra watches the PRs it opens. New conversation comments, reviews (changes-requested / non-empty comments), and inline review-thread comments trigger the selected agent backend to address them on the same branch and push a follow-up commit — inline comments are passed with their file:line so it knows exactly where each note applies. Comments that are commands directed at other bots (e.g., /dependabot rebase) are filtered out automatically. Each follow-up push includes per-finding replies on each review thread, explaining what changed and why.
Auto-iterate: CI failures
The same watcher reads each PR's check status. Once every check on the head commit has completed and at least one failed, Noctra fetches the failed-step logs and asks the selected agent backend to reproduce and fix them. CI is keyed by commit SHA, so it acts at most once per commit; review feedback and CI fixes share the MAX_PR_ITERATIONS budget and, when both are pending, are handled in one re-engagement.
Review gate
Provide GEMINI_API_KEY to enable an optional second-model review: before the PR opens, Gemini reviews the diff against the ticket. If it flags issues, the selected agent backend gets up to MAX_REVIEW_RETRIES fix passes. When the review passes, the gate posts inline, line-anchored comments on the PR — pinned to the exact lines it reviewed — so reviewers see what the second model checked. Leave the key empty to skip the gate entirely.
Learning loop
Noctra gets better at each repo over time. When a PR it opened is merged, it diffs the human edits layered on top of its last pushed commit — the corrections a reviewer applied before merging — and uses Gemini to distill them into concise, durable per-repo lessons & conventions kept in the state DB. On later runs against that same repo, those lessons are injected into the implementation and fix prompts, so the agent stops repeating the same mistakes. It's automatic and size-bounded — nothing to configure.
Maintenance sweeps
With SWEEP_ENABLED=true, a third loop runs alongside the ticket and PR watchers: on a schedule it sweeps repos and runs upkeep no one filed a ticket for, opening a labelled PR for each change. By default it targets every repo Noctra has cloned; set SWEEP_REPOS to a comma-separated list of owner/name slugs to limit sweeps to specific repos. Built-in tasks cover lint cleanup, dead code, dependency updates, test-coverage gaps, documentation drift, API modernization, and a conservative high-confidence bug scan. Run all of them or a subset via SWEEP_TASKS.
Schedule it with a cron expression in SWEEP_SCHEDULE (e.g. 0 0 * * * for nightly at midnight) or a fixed SWEEP_INTERVAL. SWEEP_MAX_TASKS caps how many run per cycle. A round-robin planner spreads tasks across repos — instead of exhausting all tasks on one repo before moving to the next, it interleaves them so every repo gets attention within the budget. A per-repo, per-task cooldown stops a task re-running too soon — so it stays cheap across many repos. A task can decline when there's nothing to do, so it never opens an empty PR. Adding your own task is one small file in internal/sweep.
Safety guards
MAX_PR_ITERATIONS— per-PR re-engagement cap; on reaching it Noctra stops and pings you.AGENT_TIMEOUT_MINUTES— hard timeout per agent run.MAX_DISPATCHES— daily (UTC) dispatch cap that pauses until midnight;MAX_RETRIES— per-ticket retry cap.- Rate-limit detection — backs off instead of burning quota.
TRUSTED_REVIEWERS— bots are ignored unless explicitly allow-listed.
Budget & cost
Optional daily caps keep an overnight run from running away. Set MAX_DAILY_TOKENS and/or MAX_DAILY_USD; when a cap is reached, Noctra pauses dispatching and auto-resumes at the next UTC midnight. Noctra captures real token and cost usage from every agent backend, so both caps track actual consumption. On a rate limit, RATE_LIMIT_STRATEGY=pause (the default) backs off for RATE_LIMIT_COOLDOWN seconds and resumes instead of burning quota or exiting.
Telegram
Noctra fans out notifications to every platform you configure — Telegram, Slack, Discord, or all three at once. Each notifier is independent; enable one or several.
Set TELEGRAM_ENABLED=true with a TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID for PR-ready pings, failure and rate-limit alerts, and a heads-up on every PR re-engagement. The same run loop also handles inbound Telegram commands through the command dispatcher — a two-way control channel for when you're away from your desk, with no separate process to start. Only your configured chat can issue commands. TELEGRAM_VERBOSE=true adds a notification on each ticket dispatch.
| Command | What it does |
|---|---|
| /status | Active runs + session stats. |
| /tickets [project] [state] | Ticket counts by state, or list one state. |
| /ticket ENG-42 | Show a ticket's details. |
| /search-tickets <text> | Search Linear tickets by text (alias /find). |
| /requeue ENG-42 [context] | Re-queue a blocked/failed ticket, optionally with extra context. |
| /kill ENG-42 | Stop a running ticket. |
| /help | List all commands. |
Slack
Set SLACK_WEBHOOK_URL to a Slack incoming-webhook URL and Noctra posts the same status messages it sends to Telegram — PR-ready pings, failures, rate-limit alerts, and PR re-engagements. A non-empty URL is the enable signal; there is no separate flag. Create the webhook under your Slack app's Incoming Webhooks settings and add it to the target workspace and channel.
Discord
Set DISCORD_WEBHOOK_URL to a Discord channel webhook URL. Noctra posts the same status messages as Telegram and Slack — a non-empty URL enables it. Create the webhook under Channel Settings → Integrations → Webhooks. Messages longer than 2,000 characters are truncated automatically, and all @-mention parsing is disabled so untrusted ticket content can never ping your server.
Trusted reviewers
Humans are always acted on. Bot reviewers are acted on only if their GitHub login is listed in TRUSTED_REVIEWERS (case-insensitive, comma-separated). The default is empty — humans only — so a confidently-wrong bot can't drive a bad change. Bot feedback is still logged.
Post as the Noctra app
By default Noctra comments and moves tickets as the user behind LINEAR_API_KEY. To act under its own identity instead, create a Linear OAuth app and set LINEAR_OAUTH_CLIENT_ID and LINEAR_OAUTH_CLIENT_SECRET. Noctra mints an actor=app token via the client-credentials grant and auto-renews it — no refresh token, no browser flow, nothing that expires on you. If the app credential ever fails to authenticate, it falls back to the personal API key and alerts you, so an expired token can't take the agent down.
Docker
A prebuilt image is published to GHCR with git, gh, and all the agent CLIs baked in — it runs anywhere Docker does, from your laptop to a $4 VPS. Provide keys via .env; repos are routed per ticket from each Linear project's Repo: directive. In a container, use HTTPS URLs / owner/name in the directive so GH_TOKEN authenticates the clone (SSH would need a mounted key).
# keys via .env; repos routed per-ticket from each Linear # project's "Repo: owner/name" directive (see Multi-repo routing) docker run -d --name noctra --env-file .env -v "$PWD/data:/data" \ ghcr.io/ahmadalmezaal/noctra:latest docker logs -f noctra
Container auth uses API keys/tokens instead of interactive logins: LINEAR_API_KEY, AGENT_BACKEND, ANTHROPIC_API_KEY or OPENAI_API_KEY, and GH_TOKEN. Everything mutable — repo cache, worktrees, logs, PR cursor — lives under the mounted /data volume, so restarts keep their state. docker compose up -d works too.
Cloud
Always-on, no hardware. Templates for Fly.io, Render, Railway, and DigitalOcean each deploy the GHCR image with the same secrets as the Docker setup. Repos route per ticket via the project Repo: directive — nothing to mount or configure; use HTTPS URLs / owner/name so GH_TOKEN authenticates the clone.
All four persist /data (Fly volume / Render disk / Railway volume / droplet disk) so restarts keep state.
Running on a Raspberry Pi
Easiest: grab the prebuilt linux_arm64 (Pi 4 / 5, 64-bit OS) or linux_armv7 (Pi 3 / 32-bit OS) archive from Releases — no Go toolchain on the Pi needed. Prefer to cross-compile yourself? Noctra is a single static binary — build it and copy it over.
# Pi 4 / 5 (64-bit OS) GOOS=linux GOARCH=arm64 go build -o noctra ./cmd/noctra # Pi 3 / 32-bit OS GOOS=linux GOARCH=arm GOARM=7 go build -o noctra ./cmd/noctra
Then point your cron or service at the binary. No runtime dependencies.
Environment variables
| Variable | Purpose |
|---|---|
| LINEAR_API_KEY | Linear API key Noctra polls with. |
| LINEAR_TEAM_KEY | Team prefix (e.g. ENG) whose issues are watched. |
| AGENT_BACKEND | Coding agent that implements tickets: claude (default), codex, copilot, or agy. |
| TICKET_SOURCES | Comma-separated list of systems to poll for work: linear (default), github, jira. Example: linear,jira,github. |
| GITHUB_ISSUES_REPOS | Comma-separated owner/name repos to scan when github is an active ticket source (e.g. your-org/your-repo). |
| GITHUB_TRIGGER_LABEL | Label on GitHub Issues that marks an issue ready for Noctra to pick up. Falls back to TRIGGER_LABEL if unset. |
| JIRA_BASE_URL | Jira Cloud site URL when jira is an active ticket source (e.g. https://your-org.atlassian.net). |
| JIRA_USER_EMAIL | Jira account email, paired with JIRA_API_TOKEN for auth. |
| JIRA_API_TOKEN | Jira Cloud API token (create at id.atlassian.com → API tokens). |
| JIRA_PROJECT | Jira project key whose issues are polled via JQL (e.g. PROJ). |
| JIRA_TRIGGER_STATUS | Jira status that marks an issue ready to build (e.g. To Do). |
| JIRA_TRIGGER_LABEL | Jira label that triggers dispatch, as an alternative to status. |
| JIRA_IN_REVIEW_STATUS | Jira status set once the PR is opened (e.g. In Review). |
| TRIGGER_MODE | Pick up work by state (column) or label (Linear only). Default: state. |
| TRIGGER_STATE | State that marks a ticket ready to build (default: Next). |
| TRIGGER_LABEL | Label that marks a ticket ready to build when label mode is enabled. |
| IN_REVIEW_STATE | State set once the PR is opened (default: In Review). |
| REPO_PATH | Last-resort single-repo fallback when a project has no Repo: directive. |
| MAIN_BRANCH | Default base branch; overridden per-repo by a project Branch: directive. |
| MAX_CONCURRENT | How many tickets run in parallel. |
| POLL_INTERVAL | Seconds between Linear polls. |
| USE_AGENT_TEAMS | Claude-only: a lead agent delegates work to teammates in parallel. |
| MAX_DISPATCHES | Daily (UTC) cap on dispatches; when hit, dispatching pauses and auto-resumes at the next midnight (0 = unlimited). |
| MAX_RETRIES | Implementation retries before giving up on a ticket. |
| AGENT_TIMEOUT_MINUTES | Hard timeout for a single agent run. |
| GEMINI_API_KEY | Enables the optional second-model review gate. |
| GEMINI_MODE | How the review gate reaches Gemini: api (default, uses the key) or cli (local gemini CLI / Google login). |
| GEMINI_MODEL | Gemini model for the review gate. |
| MAX_REVIEW_RETRIES | Fix passes the agent gets after Gemini flags issues. |
| TELEGRAM_ENABLED | Turn Telegram notifications on/off. |
| TELEGRAM_BOT_TOKEN | Bot token for notifications. |
| TELEGRAM_CHAT_ID | Chat the bot posts to. |
| TELEGRAM_VERBOSE | Also notify on every ticket dispatch. |
| SLACK_WEBHOOK_URL | Slack incoming-webhook URL; non-empty enables Slack notifications (no separate flag). |
| DISCORD_WEBHOOK_URL | Discord channel webhook URL; non-empty enables Discord notifications (no separate flag). |
| AUTO_ITERATE_PRS | Watch opened PRs and re-engage on feedback + CI. |
| MAX_PR_ITERATIONS | Per-PR re-engagement cap (shared: review + CI). |
| PR_POLL_INTERVAL | Seconds between PR scans. |
| STATE_DB | Path to the SQLite store for PR cursors, sweep cooldowns, and OAuth state (default: ~/.noctra/state.db). |
| STATE_FILE | Legacy JSON state file, migrated into STATE_DB on first run (default: ~/.noctra-state.json). |
| DASHBOARD_ADDR | Listen address for the dashboard HTTP server (e.g., :8080). If only a port is given, Noctra binds to 127.0.0.1. Empty = dashboard disabled. |
| DASHBOARD_TOKEN | Bearer token for viewer-tier (read-only) dashboard access (header or ?token= query param). The server refuses to start without it. |
| DASHBOARD_ADMIN_TOKEN | Bearer token for operator-tier dashboard access; grants viewer access plus controls (kill, requeue, pause, retry). |
| TRUSTED_REVIEWERS | CSV of bot logins to act on; empty = humans only. |
| SWEEP_ENABLED | Turn on autonomous maintenance sweeps (off by default). |
| SWEEP_REPOS | CSV of 'owner/name' slugs to sweep; empty = all cloned repos. |
| SWEEP_TASKS | CSV of sweep tasks to run; empty = all (lint-cleanup, dead-code, deps-update, test-coverage, doc-drift, modernize, bug-scan). |
| SWEEP_SCHEDULE | Cron expression for when to sweep (e.g. '0 0 * * *' for daily at midnight). Empty = use SWEEP_INTERVAL. |
| SWEEP_INTERVAL | Seconds between sweeps when no cron is set (default: 86400 = 24h). |
| SWEEP_MAX_TASKS | Cap on sweep tasks dispatched per cycle (default: 5). |
| MAX_DAILY_TOKENS | Daily token cap across agent runs; on hit, dispatching pauses and auto-resumes. 0 = unlimited. |
| MAX_DAILY_USD | Daily cost cap in USD based on real usage reported by the agent backend; 0 = unlimited. |
| RATE_LIMIT_STRATEGY | On a rate limit: pause (default, back off then resume) or shutdown. |
| RATE_LIMIT_COOLDOWN | Seconds to pause after a rate limit when strategy is pause (default: 1800). |
| LINEAR_OAUTH_CLIENT_ID | OAuth app client id to post as the Noctra app (actor=app); paired with the secret below. |
| LINEAR_OAUTH_CLIENT_SECRET | OAuth app client secret; Noctra mints + auto-renews a 30-day app token from these two. |
| ANTHROPIC_API_KEY | Claude auth when running headless (Docker/cloud), instead of an interactive login. |
| OPENAI_API_KEY | Codex auth when running headless, instead of codex login. |
| GH_TOKEN | PAT for gh PR creation + git push in containers (repo + PR scope); also authenticates the Copilot backend. |
| GIT_USER_NAME | Commit author name (defaults to a Noctra bot). |
| GIT_USER_EMAIL | Commit author email (defaults to a Noctra bot). |
CLI commands
noctra— start the poll loop (default).noctra setup— interactive configuration wizard for~/.noctra/(includes backend, Linear, Telegram/Slack/Discord, sweep schedule, and dashboard).noctra doctor— preflight tokens, local tools, repo routing, and model access (add--jsonfor machine-readable output).noctra update— self-update a release binary: download the latest, verify its checksum, and swap in place (--restartrestarts the service after).noctra install-service— write asystemd --userunit pointing at the installed binary (--startto enable, start, and keep it running after logout).noctra uninstall— remove the service and installed binary; add--purgeto also delete~/.noctra*state (config, cloned repos, worktrees, PR cursor), and--forceto skip the confirmation.noctra start/stop/restart/status— control thesystemd --userservice from the binary.noctra logs— tail the service logs (-fto follow).noctra cleanup— prune stale branches, worktrees, and old logs (add--forceto remove everything stale non-interactively).noctra completion bash|zsh— print a shell completion script.noctra version— print the build version.