Repository navigation
Releases: selfhostr/orch
Release list
v0.9.1
Moved to selfhostr/orch
The project is now simply orch and lives at github.com/selfhostr/orch. The old Theroxat/claude_orch URLs redirect here. Documentation: https://orch.selfhostr.io/docs/
New image location
Releases are now published only as:
ghcr.io/selfhostr/orch-serverghcr.io/selfhostr/orch-webghcr.io/selfhostr/orch-agent
ghcr.io/theroxat/orch-* gets no new releases. deploy/server/compose.yaml uses the new names. If your server .env pins ORCH_VERSION to an old release, set it to 0.9.1, and remove any ORCH_AGENT_IMAGE_REPO=ghcr.io/theroxat/orch-agent override (the default is now ghcr.io/selfhostr/orch-agent).
Members: switch your agent once
Agents installed with v0.9.0 or older only accept updates from ghcr.io/theroxat/orch-agent, so one-click update answers "This agent still uses the old image" for them. Run this once on the agent's machine (Linux; on macOS use sed -i ''):
cd ~/orch && sed -i 's#ghcr.io/theroxat/#ghcr.io/selfhostr/#g' compose.yaml .env && sed -i '/^ORCH_AGENT_TAG=/d' .env && docker compose pull agent && docker compose up -d agentLater updates work with one click again. See Updating agents.
Fixes
- Renamed or transferred repositories stay linked: the repository renamed/transferred, installation created and repositories added webhooks now update the project's stored full name and installation (both used to mint tokens), and restore a project that was archived after a lost access (#75)
Upgrade
Server: set ORCH_VERSION=0.9.1 (or keep latest), then from deploy/server:
git pull
docker compose pull
docker compose up -dv0.9.0
Security
- Compose guard completed (agent): strict allowlists on every key of the resolved
docker compose configmodel (services, builds, deploy, volumes, networks, secrets, configs), hardened sidecars (cap_drop: ALLplus a harmless baseline,no-new-privileges, CPU/memory/PID limits, no published ports or fixed container names) and a corpus of real compose files tested against it. It also closes a host escape: a sidecar with a writable bind of a directory could swap a path that another sidecar mounts (including a top-levelsecrets/configsfile) for a symlink to/before that mount. Such writable binds are now refused. Repositories whose compose files used one of the refused features have to adapt them (see the security docs) (#71, @laupernoe) - Private repository access re-checked: the stored project owner is only a hint. Every view and action on a private repository (usage, history, board, templates, pool, MCP tools, notifications, task offers and repository tokens) asks GitHub whether the user still has a role on it. Answers are cached, and webhooks invalidate them. A 403 or 404 denies at once. During a GitHub outage (5xx, 429, rate-limited 403 or 401), the last verified role is kept for a grace window, then access is denied (#67, @laupernoe)
- Stronger secret redaction: one shared module masks secrets in live logs (agent and server), the local run log, failure messages and notifications. It covers common token formats and every value the agent knows for a run, in its common encodings (JSON-escaped, URL, hex, base64), also when a value is split across lines or batches. All rules run in linear time (#72, @laupernoe)
- Signed webhooks: generic JSON webhooks are signed according to Standard Webhooks (
webhook-id,webhook-timestamp,webhook-signature). A secret is generated or rotated in the notification settings and is shown only once. During a rotation, the previous secret also signs for 24 hours (#70, @laupernoe)
Changes
- Several server instances: run orch behind a load balancer with Postgres as the only shared component. The dispatcher and the maintenance jobs each run on an elected leader, agent messages are relayed between instances and live output is mirrored to the instance that has viewers. Notification deliveries go through a durable queue that another instance takes over if the first one dies. The
authandagent-connectrate limits are shared across instances. See Running several server instances in the installation docs (#68, @laupernoe) - English and French interface: every page is translated through typed catalogs, and a CI check keeps the two locales in sync. English is the default; French is used only when chosen with the EN | FR switch, and the choice is remembered on the browser (#69, @laupernoe)
- Faster history search: trigram index on task titles and indexes on issue and PR numbers (#73, @laupernoe)
- Landing site: a static site in
site/, published with GitHub Pages (#66, @laupernoe)
Many thanks to @laupernoe, who wrote every pull request in this release.
New configuration
| Variable | Default | Meaning |
|---|---|---|
ORCH_REPO_ACCESS_TTL_SEC |
300 | How long a user's role on a repository is cached |
ORCH_REPO_ACCESS_GRACE_SEC |
900 | GitHub outage grace for private repositories |
NOTIFY_QUEUE_POLL_MS |
5000 | How often the notification queue is polled for retries and leftovers of other instances |
ORCH_INSTANCE_ID |
hostname | Unique name of this instance among those sharing the database |
ORCH_CLUSTER_NAME |
orch |
Instances with the same database and name cooperate |
ORCH_INSTANCE_HEARTBEAT_MS |
10000 | Instance heartbeat; an instance silent for 3 heartbeats is considered gone |
ORCH_LEADER_RETRY_MS |
5000 | How often a follower tries to take a free leadership |
All of these are optional, and the defaults suit a single instance.
Upgrade
Server: set ORCH_VERSION=0.9.0 (or keep latest), then from deploy/server:
git pull
docker compose pull
docker compose up -dgit pull also brings the new deploy/server/upstream.caddy, which compose.yaml now mounts into Caddy. Three additive migrations run on start: the pg_trgm extension and the history search indexes, the webhook signing secret columns, and the multi-instance tables. pg_trgm is a trusted extension, so the database owner can create it. For several instances, add -f compose.replicas.yaml (see the installation docs). Every instance needs the same .env, including ORCH_ENCRYPTION_KEY.
Agents: orch update gets the completed compose guard and the new redaction.
Webhook receivers: verify the signature once a secret has been generated in the notification settings, and dedupe on webhook-id.
v0.8.1
Changes
- E-mail address confirmation: notification e-mails (and test messages) only go to an address the member has confirmed through a single-use link valid 24 hours (only a hash of the token is stored). Confirmation e-mails are limited to 3 per hour and 10 per day per member (#62, @laupernoe)
- Metrics:
prom-client(deprecated) replaced by its official successor@prometheus-io/client; existing/metricsseries are unchanged, two event-loop utilization series are added (#61, @laupernoe) - Accessibility: the welcome wizard moves focus to the step title as soon as the step is shown instead of after its animation, so a Tab pressed during the transition is no longer undone; this also fixes a flaky browser test (#63, @laupernoe)
Many thanks to @laupernoe, who wrote every pull request in this release.
Upgrade
Server: set ORCH_VERSION=0.8.1 (or keep latest), then from deploy/server:
git pull
docker compose pull
docker compose up -dOne additive migration applies on start (e-mail confirmation columns). E-mail addresses saved before this release are pending: members must press Resend confirmation in their notification settings and open the link before e-mail notifications resume. Without SMTP_HOST nothing changes. Agents: orch update.
v0.8.0
Highlights
- Live task logs: follow a running task's output in the web UI, streamed from the agent (SSE) (#48, @laupernoe)
- Notifications: in-app bell plus Slack, Discord, generic webhook and e-mail channels, with per-event preferences (#49, @laupernoe)
- Web Push: browser notifications, even with the tab closed (#58, @laupernoe)
- Installable app (PWA): install orch from the browser, with an offline page and a "New version available" prompt (#54, @laupernoe)
- Usage page: weighted tokens, runs, success rate and project budgets over 7/30/90 days (#50, @laupernoe)
- Task history: search, filters and pagination over every task that ran (#51, @laupernoe)
- Task templates: reusable task skeletons with
{{placeholders}}, personal or shared with a project, plus 4 built-in ones (#60, @laupernoe)
Operations
- Observability: Prometheus
/metrics(internal network only),/readyz, optional OpenTelemetry traces and a ready-made Prometheus + Grafana stack indeploy/observability(#53, @laupernoe) - Scheduled backups: cron schedule, daily/weekly/monthly rotation, optional age encryption and S3-compatible upload, and
backup/restore.shto restore (#52, @laupernoe) - Rate limiting on every route family, with
429+Retry-Afterand a per-connection cap on agent messages (#55, @laupernoe) - Server image down from 904 MB to ~384 MB (#59, @laupernoe)
- Dispatcher loads one snapshot per tick instead of queries per task × member (#56, @laupernoe)
- Faster browser-test CI (#57, @laupernoe) and Renovate for dependency updates, every PR reviewed by hand (#47, @laupernoe)
Many thanks to @laupernoe, who wrote every pull request in this release.
New optional settings (deploy/server/.env)
Everything below is off or has a safe default; nothing needs to be set to upgrade.
| Feature | Variables |
|---|---|
| E-mail notifications | SMTP_HOST (enables the channel), SMTP_PORT, SMTP_SECURE, SMTP_USER, SMTP_PASSWORD, SMTP_FROM |
| Web Push | VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_SUBJECT (all three; generate the pair with docker run --rm node:22-alpine npx -y web-push generate-vapid-keys) |
| Metrics / tracing | METRICS_ENABLED, METRICS_TOKEN, OTEL_EXPORTER_OTLP_ENDPOINT; GRAFANA_ADMIN_PASSWORD for deploy/observability |
| Backups | BACKUP_SCHEDULE, BACKUP_KEEP_DAILY/WEEKLY/MONTHLY, BACKUP_VOLUME, BACKUP_AGE_RECIPIENT, BACKUP_S3_*, POSTGRES_MAJOR; ORCH_RESTORE_TMPDIR for restore.sh |
| Rate limits | RATE_LIMIT_ENABLED, RATE_LIMIT_WINDOW_SEC, RATE_LIMIT_{API,AUTH,WEBHOOK,MCP,HEAVY,STREAM,AGENT_CONNECT}_MAX, RATE_LIMIT_AGENT_MSG_PER_SEC (see docs/configuration.md) |
| Notification delivery | NOTIFY_MAX_ATTEMPTS, NOTIFY_RETRY_BASE_MS, NOTIFY_RETENTION_DAYS |
BACKUP_HOUR_UTC and BACKUP_RETENTION_DAYS still work (they map to BACKUP_SCHEDULE and BACKUP_KEEP_DAILY).
Upgrade
Server: set ORCH_VERSION=0.8.0 (or keep latest), then from deploy/server:
git pull
docker compose pull
docker compose up -d
docker compose up -d --build backup # the backup image is built locally and changed in this releaseFive additive migrations apply on start (notifications, task history index, Web Push subscriptions, task templates). The Caddy routes changed (/metrics and /readyz are never public, PWA files are revalidated), so git pull before up -d. Agents: orch update.
Optional: to collect metrics, set METRICS_ENABLED=true and start deploy/observability (see the Observability docs page).
v0.7.0
Highlights
- Documentation site on Read the Docs: https://claude-orch.readthedocs.io (quickstart, installation, concepts, CLI and MCP reference, configuration, security model, FAQ), built in CI with warnings as errors (#36, @laupernoe)
- Web UI: Documentation and GitHub links in the header (#44) and a Claude orange palette, with ochre for "pending" (#45), both by @laupernoe
- README refreshed, with screenshots and a link to the docs (#42)
Under the hood
- Server: new indexes for the dispatcher hot paths (additive migration
20261031000000_hot_path_indexes), andGET /api/tasksnow runs one query for every visible project (#41, @laupernoe) - Agent image: multi-stage build (#40, @laupernoe)
- CI: production images built in parallel, actions bumped (#39, @laupernoe)
- Root
pnpmscripts,noUnusedLocals/noUnusedParameters, dead code removed (#43, @laupernoe)
Many thanks to @laupernoe for nearly all of this release.
Upgrade
Server: set ORCH_VERSION=0.7.0 (or keep latest), then docker compose pull && docker compose up -d; the migration applies on start. Agents: orch update.
v0.6.1
Web UI fixes found by a new browser (Playwright) test suite.
Fixes
- No more CSP
script-src evalviolation on every page load (zod runs jitless in the browser). - OAuth consent: a double click on Approve sends a single request.
- Backlog: Retry only appears on tasks that failed since the last retry or need attention; the Import label no longer shows literal backticks; the board fits at 1280px without horizontal scroll.
- Unknown URLs show a "Page not found" page with a link home.
- /welcome wizard: Log out next to Exit setup.
- Pool: each member card lists their machines (paused, online, offline) and shows Paused when every online machine is paused (additive
machinesfield inGET /api/pool). - Color contrast fixes (ok badge, wizard status, revoked token card, badges) in both themes.
Tests / CI
- New
test/uiPlaywright suite, run with./dev test:uiand in CI (screenshots and report uploaded on failure). CSP violations and axe serious/critical findings fail it.
Update: docker compose pull && docker compose up -d (server), orch update (agent).
v0.6.0
v0.6.0: new look, modern stack, owners in their own pool
Huge thanks to @laupernoe (Noé Lauper), who did most of this release: the full dependency upgrade (#5), the web redesign (#22), and four web fixes (#24, #26, #27, #28).
Web redesign (#22, @laupernoe)
- A new "signal board" design for the whole web app, with self-hosted Overpass fonts. The CSP is unchanged.
- Everything from v0.5.x carries over in the new style: the outdated-agent banner, failed-attempt info, Retry, why-not-running, the Claude login state, onboarding, Access and the version footer.
Web fixes (@laupernoe)
/backlog?task=<id>highlights the linked task (#24).- Removing a project refreshes the list of already-added repositories (#26).
- Signing in takes you back to the page you asked for (#27).
- The Access page clears an old error after a success (#28).
Dependency upgrade (#5, @laupernoe)
- React 19, Vite 8, Zod 4, Prisma 7, pnpm 12 and Node 24, in every image.
- Prisma 7 was checked against a copy of the production database: no schema drift and no data change.
New: owners can join their own pool (#34)
- Each project card in the owner view has a Participate in my pool block with a budget % and a mode. An owner's own participation is approved automatically.
- The onboarding step "Add your repository" offers the same block as an optional follow-up.
- The dispatcher treats the owner like any other participant. The consent rule is unchanged: no work runs without an active participation that the member saved.
New: clearer member rejection (#34)
- Owners can add an optional reason when rejecting. The member sees it next to "Rejected by owner".
- The project card lists rejected members, with Undo: back to pending and Undo: approve.
- When a previously rejected member asks again, the owner's pending list shows previously rejected (reason).
- Everything is audited:
participation.rejectedwith the reason,participation.rejection_undone, andreappliedandownerAutoApprovedon updates. This release also fixes the audit action nameparticipation.rejectd, which is nowparticipation.rejected. - Migration
20261030000000_participation_rejectionadds two nullable columns. The README now documents how migrations are named.
Upgrade
Update the server first (docker compose pull && docker compose up -d). It applies the new migration automatically. Then update each agent with orch update.
v0.5.2
Fix: Claude 401 on auto runs ("OAuth access token is invalid")
Root cause. orch login read the token from the claude setup-token screen by stripping ANSI codes from the terminal stream. Claude Code's renderer is differential: it moves the cursor and skips cells that did not change. As a result, the saved token could silently lose characters, and every run then failed with a 401. v0.5.1 also cleared the server's "login invalid" flag on any report that said a token was present, which caused a loop of re-offers and new failures.
What changed
orch login- Reads the token from a real terminal emulation of the setup screen.
- Checks its shape (
sk-ant-oat…, length) before saving. - If it cannot read the token reliably, asks you to paste it (hidden input). Wrapped pastes are handled.
- Then verifies the token with a real one-turn Claude call and prints
✓ Claude login verifiedor✗ Claude rejected this token (401). Run orch login again.
orch doctor(new): shows the Claude binary and version, whether the token is present and well-formed, and runs the same real check.- Automatic re-check: the agent re-checks the token whenever the token file changes. It stores a hash, never the token. The result goes to the server as the new
claude.authmessage. - Server: only a successful real check, or a successful run, clears the "Claude login invalid" flag. A flagged agent gets no automatic offers. My Server, Pool and the getting-started checklist show when the login was last verified and why it failed.
- Task sandbox:
ANTHROPIC_API_KEY,ANTHROPIC_AUTH_TOKEN,ANTHROPIC_BASE_URLand the provider switches are blanked, so a repository image orcontainerEnvcannot override your Claude token. - CI: the agent image is smoke-tested (
$ORCH_CLAUDE_BIN --version).
Upgrade
Update the server first, then each agent with orch update. Then run orch login again (the stored token may be corrupt) and orch doctor.
Agents still on v0.5.1 that are flagged stay flagged until they update and pass the check.
v0.5.1
Highlights
Stuck backlog fix (#32)
- The agent now classifies failures as
env,claude_auth,rate_limited,timeoutortask. Onlytaskandtimeoutcount toward the 24 h shut-out. - An expired Claude login flags the agent ("Claude login expired: run
orch login") and pauses AUTO offers. - Environment setup failures mark the task "needs attention" after 3 attempts.
- Backlog cards show "Failed N times: last error" and a Retry button (owner/creator, audited). Also available as
POST /api/tasks/:id/retryand the MCP toolretry_task. - New "Why isn't this running?" panel, computed by the dispatcher itself.
- #31: duplicate
agent.update_doneaudit entries are now deduplicated.
Other changes since v0.5.0
- fix(pool): a submitter who does not participate can see the pool of the project (#25) - Thanks @laupernoe
- fix(agent): a re-run can push over its task branch from an earlier attempt (#21) - Thanks @laupernoe
- fix(agent): fence server cancels during the push and the preparation (#19) - Thanks @laupernoe
- fix(agent): a stop during a run no longer loses its task.finished (#17) - Thanks @laupernoe
- fix(agents): a duplicate ack no longer turns a refused accept or claim into a success (#16) - Thanks @laupernoe
- fix(participation): concurrent PUTs could push a member's budget above 100% (#15) - Thanks @laupernoe
Upgrade notes
- The DB migration
20261028000000_failure_classesapplies automatically. - Update agents with
orch update(old agents are still classified server-side).
v0.5.0
Highlights
- One-click agent update (#18): the web app shows a banner when an agent runs an older version and can update it with one click. The server version now appears in the footer.
- Hardened task sandbox (#23): the compose guard now accepts only allowlisted keys, uses a real YAML parser, and refuses
provider, privileged hooks,use_api_socket,gpus, foreign networks, volume and network driver options, and local build caches. Every symlink in the committed tree is checked, and the whole compose project is paused before a commit. This supersedes #8 and #9, closes #14, and includes commits by @laupernoe.
Fixes
- #20: the env_file override now applies to every compose call (build/up). Compose files no longer set a fixed
container_name. E2E runs are isolated withORCH_E2E_PROJECT. - #7: a connected agent loses access when its owner's access is removed. A GitHub outage gets a grace period, and suspension (4403) is not fatal. Thanks @laupernoe.
- #11: if
orch donefails, the manual session keeps its.git. Thanks @laupernoe. - #12: the avatar fallback on the admin Access page was blocked by the CSP. Thanks @laupernoe.
- #13: the OAuth consent page can no longer be submitted twice, and the server issues one authorization code per consent request. Thanks @laupernoe.
Known follow-up
Many thanks to @laupernoe for the review and the fixes in this release.