On this page

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_BACKENDClaude 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 ghgh 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 worktreedispatch 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.

CommandWhat it does
/statusActive runs + session stats.
/tickets [project] [state]Ticket counts by state, or list one state.
/ticket ENG-42Show 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-42Stop a running ticket.
/helpList 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

VariablePurpose
LINEAR_API_KEYLinear API key Noctra polls with.
LINEAR_TEAM_KEYTeam prefix (e.g. ENG) whose issues are watched.
AGENT_BACKENDCoding agent that implements tickets: claude (default), codex, copilot, or agy.
TICKET_SOURCESComma-separated list of systems to poll for work: linear (default), github, jira. Example: linear,jira,github.
GITHUB_ISSUES_REPOSComma-separated owner/name repos to scan when github is an active ticket source (e.g. your-org/your-repo).
GITHUB_TRIGGER_LABELLabel on GitHub Issues that marks an issue ready for Noctra to pick up. Falls back to TRIGGER_LABEL if unset.
JIRA_BASE_URLJira Cloud site URL when jira is an active ticket source (e.g. https://your-org.atlassian.net).
JIRA_USER_EMAILJira account email, paired with JIRA_API_TOKEN for auth.
JIRA_API_TOKENJira Cloud API token (create at id.atlassian.com → API tokens).
JIRA_PROJECTJira project key whose issues are polled via JQL (e.g. PROJ).
JIRA_TRIGGER_STATUSJira status that marks an issue ready to build (e.g. To Do).
JIRA_TRIGGER_LABELJira label that triggers dispatch, as an alternative to status.
JIRA_IN_REVIEW_STATUSJira status set once the PR is opened (e.g. In Review).
TRIGGER_MODEPick up work by state (column) or label (Linear only). Default: state.
TRIGGER_STATEState that marks a ticket ready to build (default: Next).
TRIGGER_LABELLabel that marks a ticket ready to build when label mode is enabled.
IN_REVIEW_STATEState set once the PR is opened (default: In Review).
REPO_PATHLast-resort single-repo fallback when a project has no Repo: directive.
MAIN_BRANCHDefault base branch; overridden per-repo by a project Branch: directive.
MAX_CONCURRENTHow many tickets run in parallel.
POLL_INTERVALSeconds between Linear polls.
USE_AGENT_TEAMSClaude-only: a lead agent delegates work to teammates in parallel.
MAX_DISPATCHESDaily (UTC) cap on dispatches; when hit, dispatching pauses and auto-resumes at the next midnight (0 = unlimited).
MAX_RETRIESImplementation retries before giving up on a ticket.
AGENT_TIMEOUT_MINUTESHard timeout for a single agent run.
GEMINI_API_KEYEnables the optional second-model review gate.
GEMINI_MODEHow the review gate reaches Gemini: api (default, uses the key) or cli (local gemini CLI / Google login).
GEMINI_MODELGemini model for the review gate.
MAX_REVIEW_RETRIESFix passes the agent gets after Gemini flags issues.
TELEGRAM_ENABLEDTurn Telegram notifications on/off.
TELEGRAM_BOT_TOKENBot token for notifications.
TELEGRAM_CHAT_IDChat the bot posts to.
TELEGRAM_VERBOSEAlso notify on every ticket dispatch.
SLACK_WEBHOOK_URLSlack incoming-webhook URL; non-empty enables Slack notifications (no separate flag).
DISCORD_WEBHOOK_URLDiscord channel webhook URL; non-empty enables Discord notifications (no separate flag).
AUTO_ITERATE_PRSWatch opened PRs and re-engage on feedback + CI.
MAX_PR_ITERATIONSPer-PR re-engagement cap (shared: review + CI).
PR_POLL_INTERVALSeconds between PR scans.
STATE_DBPath to the SQLite store for PR cursors, sweep cooldowns, and OAuth state (default: ~/.noctra/state.db).
STATE_FILELegacy JSON state file, migrated into STATE_DB on first run (default: ~/.noctra-state.json).
DASHBOARD_ADDRListen 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_TOKENBearer token for viewer-tier (read-only) dashboard access (header or ?token= query param). The server refuses to start without it.
DASHBOARD_ADMIN_TOKENBearer token for operator-tier dashboard access; grants viewer access plus controls (kill, requeue, pause, retry).
TRUSTED_REVIEWERSCSV of bot logins to act on; empty = humans only.
SWEEP_ENABLEDTurn on autonomous maintenance sweeps (off by default).
SWEEP_REPOSCSV of 'owner/name' slugs to sweep; empty = all cloned repos.
SWEEP_TASKSCSV of sweep tasks to run; empty = all (lint-cleanup, dead-code, deps-update, test-coverage, doc-drift, modernize, bug-scan).
SWEEP_SCHEDULECron expression for when to sweep (e.g. '0 0 * * *' for daily at midnight). Empty = use SWEEP_INTERVAL.
SWEEP_INTERVALSeconds between sweeps when no cron is set (default: 86400 = 24h).
SWEEP_MAX_TASKSCap on sweep tasks dispatched per cycle (default: 5).
MAX_DAILY_TOKENSDaily token cap across agent runs; on hit, dispatching pauses and auto-resumes. 0 = unlimited.
MAX_DAILY_USDDaily cost cap in USD based on real usage reported by the agent backend; 0 = unlimited.
RATE_LIMIT_STRATEGYOn a rate limit: pause (default, back off then resume) or shutdown.
RATE_LIMIT_COOLDOWNSeconds to pause after a rate limit when strategy is pause (default: 1800).
LINEAR_OAUTH_CLIENT_IDOAuth app client id to post as the Noctra app (actor=app); paired with the secret below.
LINEAR_OAUTH_CLIENT_SECRETOAuth app client secret; Noctra mints + auto-renews a 30-day app token from these two.
ANTHROPIC_API_KEYClaude auth when running headless (Docker/cloud), instead of an interactive login.
OPENAI_API_KEYCodex auth when running headless, instead of codex login.
GH_TOKENPAT for gh PR creation + git push in containers (repo + PR scope); also authenticates the Copilot backend.
GIT_USER_NAMECommit author name (defaults to a Noctra bot).
GIT_USER_EMAILCommit 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 --json for machine-readable output).
  • noctra update — self-update a release binary: download the latest, verify its checksum, and swap in place (--restart restarts the service after).
  • noctra install-service — write a systemd --user unit pointing at the installed binary (--start to enable, start, and keep it running after logout).
  • noctra uninstall — remove the service and installed binary; add --purge to also delete ~/.noctra* state (config, cloned repos, worktrees, PR cursor), and --force to skip the confirmation.
  • noctra start / stop / restart / status — control the systemd --user service from the binary.
  • noctra logs — tail the service logs (-f to follow).
  • noctra cleanup — prune stale branches, worktrees, and old logs (add --force to remove everything stale non-interactively).
  • noctra completion bash|zsh — print a shell completion script.
  • noctra version — print the build version.