Run many coding agents while sharing a limited number of heavy local job slots. Agents can read, reason, and edit concurrently. Builds, test suites, type checks, and other expensive commands wait their turn.
The macOS menu-bar app shows what is running, what is waiting, and which project and branch each job belongs to. An optional private web dashboard works from a phone over home Wi-Fi or a VPN.
| Mac menu-bar queue | Phone dashboard |
|---|---|
![]() |
![]() |
Screenshots use sample jobs. The Mac image uses the app's safe preview mode.
- Use macOS 15 or later on an Apple Silicon or Intel Mac.
- Download
Agent-Queue-<version>-universal.dmg(or the ZIP) from Releases. - Open the DMG and drag Agent Queue onto Applications. To install for your account only, drop it in
~/Applicationsinstead. - Open Agent Queue from Applications. It sets itself up silently and appears in the menu bar.
The app is self-contained: the menu app, the agent-heavy and agent-remote command-line tools, the queue daemon, the resource controller and the HTTPS runtime all live inside Agent Queue.app. There is no installer and nothing is copied out of the bundle. On first launch the app creates its private data directories and adds agent-heavy and agent-remote wrappers to ~/.local/bin. On every launch it then runs the queue itself: it starts and supervises the queue daemon, the resource controller and, when phone access is on, the phone-access service as its own child processes. Only the app's login item (open at login) is registered with macOS Service Management. Xcode, Go, Python, Pueue and Caddy are not required, and setup never uses sudo.
Opening the app immediately adds its menu-bar icon, which shows "Starting…" while setup finishes in the background and the queue comes up, on both first launch and upgrades. There is no setup wizard or progress window. Setup wording, diagnostics and Retry appear in the menu only if something actually fails. macOS may show a "Background Items Added" notification for the login item; approving it in System Settings > General > Login Items only decides whether Agent Queue opens at login and never delays the queue. Setup also unregisters any queue, resource or phone-access background services that older versions registered with macOS, including ones stuck waiting for approval. Only the explicit --setup diagnostic mode opens a recovery window.
To upgrade, quit Agent Queue, drag the new version over the old one and choose Replace, then open it. Queue history, settings, credentials and certificates are kept. Running jobs keep going after you quit. A terminal attached to a job through agent-heavy run keeps retrying for about two minutes while the daemon is away. A quit, drag and reopen usually takes longer, so it then exits with status 125 ("may still be running", for jobs that are running or queued), unless the queue store shows that the job finished, in which case it reports the job's own exit status. Run agent-heavy drain first and the caller waits through the whole restart instead (see Drain the queue). The new version records each running job's outcome and replays nothing. If jobs were running, its queue starts paused, and queued jobs then run after agent-heavy resume; with no drain they have no caller attached, while during a drain their callers stay attached. If a queue daemon from an older version is still running, setup waits for its running and queued jobs to finish before replacing it. See upgrade recovery.
If you move the app between /Applications and ~/Applications, delete or replace the old copy first. Setup refuses to adopt a second copy while the recorded one still exists.
To remove Agent Queue, finish queued and running jobs, then run this command and move the app to the Trash. It checks that the queue is drained, then quits the app, which stops its queue processes, and removes its login item. If jobs are still active it refuses and leaves the app running. If it refuses after quitting the app, open Agent Queue, then run the command again. If it cannot verify the login item, which can happen after a brew upgrade that was never opened or a reset of macOS background items, quit the app, run agent-heavy install --app-bundle "/Applications/Agent Queue.app" to register the item again, then run the command again.
agent-heavy install --app-bundle "/Applications/Agent Queue.app" --unregister-servicesUse the app's actual location if it differs. Private data under ~/.local/share/agent-heavy, ~/.local/state/agent-heavy and ~/.config/agent-heavy is preserved. That includes a drain: if you ran agent-heavy drain, run agent-heavy undrain before you remove the app (or after a reinstall; it removes a stale drain file even without a daemon). Otherwise the preserved drain.json keeps new agent-heavy run calls waiting until it expires.
brew install --cask dunky13/agent-job-queue/agent-job-queueThe cask places Agent Queue.app in Applications and nothing else; open it to use the menu bar while setup finishes automatically. brew upgrade --cask dunky13/agent-job-queue/agent-job-queue replaces the app, and the next launch immediately shows the menu bar and completes the upgrade in the background. Homebrew verifies the download's SHA-256 and keeps quarantine so macOS assesses the notarized app. Maintainer setup is documented in Verified Homebrew delivery.
Releases from the distribution workflow are Developer ID signed, notarized and stapled (app and DMG). Quarantine is never removed. Because the app runs only helpers that ship inside the signed app, as its own child processes, and registers only its own login item, endpoint security tools see a notarized app from Applications, not a separate process copying executables. See Apple signing and notarization for verification details.
If macOS blocks a downloaded build you trust, open System Settings > Privacy & Security, choose Open Anyway next to Agent Queue, and confirm. Do not clear quarantine to work around a failed verification of a release advertised as notarized. See Apple's opening instructions.
Earlier releases shipped Agent Job Queue Installer.app, which installed ~/Applications/Agent Queue.app and ran the services from that copy. That location stays recorded as the managed one, and setup refuses a new copy placed anywhere else (for example /Applications from the new DMG or cask) while the recorded copy exists. Either:
-
Quit Agent Queue, drag the new app over
~/Applications/Agent Queue.appand choose Replace, then open it; or -
Stop the old services, move the old app to the Trash, then open the new app wherever you installed it:
~/.local/bin/agent-heavy install --app-bundle "$HOME/Applications/Agent Queue.app" --unregister-services
Homebrew users upgrading from the installer cask must use the second route: the old cask left ~/Applications/Agent Queue.app in place, so run the command above, move that app to the Trash, then brew upgrade --cask dunky13/agent-job-queue/agent-job-queue (or install it) and open the app from Applications. Queue history, settings, credentials and certificates are kept. If jobs are queued or running, finish them first or see upgrade recovery.
Choose About and updates from the queue's gear menu to see the running app and installed controller versions. Check for updates finds published builds. Download update downloads and verifies the selected build, places a verified copy of Agent Queue.app in your Downloads folder and reveals it in Finder. Quit Agent Queue, drag the new copy into Applications and choose Replace; opening it finishes the update. Checking never installs anything and tracked downgrades are refused. Details are in Installed version and updates.
Open Agent integration from the queue's gear menu to add queue instructions for Codex and Claude Code. Select the runners you use, review the instructions, then choose Save instructions. Start new agent sessions afterward. This is optional and never blocks installation or opening the queue.
Setup shows each instruction file and whether this app has configured it. It preserves existing text, adds its own marked section, and makes a private backup before changing an existing file. Deselecting a runner removes only that section. Existing custom rules and shared imports remain in place; the status describes this app's section only.
The command line supports the same actions:
agent-heavy agents status
agent-heavy agents install --runner codex
agent-heavy agents install --runner claude
agent-heavy agents remove --runner codexCodex uses its global AGENTS.md, or its existing nonempty AGENTS.override.md when that takes precedence. Claude Code uses its user CLAUDE.md. The CLI honors CODEX_HOME and CLAUDE_CONFIG_DIR; run it from the runner's shell when using a custom profile. The app shows the paths visible in its own environment. See the official Codex instruction discovery and Claude Code memory documentation.
These instructions guide agents; they do not intercept commands or enforce queue usage. Other runners can use the shared rule below. Installation and upgrades do not change agent instructions until you explicitly save your selections.
agent-heavy run --label "Build and unit tests" -- npm run build
agent-heavy -- npm run typecheck
agent-heavy -- go test -p 2 ./...Commands run in their original working directory with their literal arguments and environment. The wrapper waits, streams output, and returns the command's exit status. Several repositories, worktrees, terminals, and agents share the same queue.
Queue resource-intensive parallel work. Small serial compilations, focused tests, source inspection, and editing can run directly. Keep persistent development servers and watch modes outside the queue and account for their memory separately. Do not nest queue wrappers. Jobs are noninteractive and receive EOF on stdin; pass scripts as files or arguments.
For other runners, add this rule to their shared instructions:
Run resource-intensive builds, broad tests, type checks, browser suites, and
expensive generators through agent-heavy -- <command> [arguments...].
Use the same shared queue across repositories and worktrees. Keep each tool's
internal worker count conservative. Run small focused checks directly.
The Go queue daemon owns submitted jobs independently of the calling terminal. The menu bar app starts and supervises the daemon, so the queue is available while the app is running; without it, agent-heavy reports that the queue daemon is unavailable instead of running the command directly. While a drain is active the daemon is not needed to decide what happens: the default wait caller waits, --on-drain fail exits 75, and only the explicit --on-drain direct choice runs the command outside the queue (see Drain the queue). Closing the window does not stop jobs. Quitting the app stops the daemon, but running job processes keep running (see below). Abruptly losing a submitter leaves its accepted job visible until it finishes or is explicitly cancelled.
The menu-bar app provides project and branch labels, queue positions, elapsed time, results, job details, and bounded log tails. Background checks leave unchanged content in place. Details and confirmations stay within the queue panel.
Click the menu-bar icon to open the queue popover. Right-click or Control-click it for Open window, learned workloads, About and updates, agent integration, remote access settings, and Quit. Quit stops the queue: the app sends its queue daemon, resource controller and phone-access service SIGTERM, then SIGKILL to any that do not exit within a grace period. Running job processes are not stopped. If jobs were active, the queue comes back paused when you next open the app, as after any unclean daemon restart. Double-click the icon to open or focus the main queue window. The same settings remain available in the popover's gear menu.
Use the app or CLI to change future scheduling:
agent-heavy status
agent-heavy status --json
agent-heavy limit 2
agent-heavy next 17
agent-heavy last 17
agent-heavy start 17
agent-heavy cancel 17Replace 17 with a real job ID. The default parallel limit is one, adjustable from one through eight. Lowering the limit lets active jobs finish and holds future jobs. Start now starts one selected job alongside active work without changing the limit. Cancellation targets that job's positively identified owned processes.
A steady stream of submissions can keep the queue from ever emptying. agent-heavy drain closes intake without stopping the queue, and also lets callers survive an app restart:
agent-heavy drain [--for DURATION] [--wait] [--cancel-queued] [--cancel-running]
agent-heavy undrain [--force]drainwrites a privatedrain.jsonwith an expiry.--forsets it (at least 1s, at most 24h) and replaces any existing expiry; without--fora new drain lasts 4h, while an already active drain keeps its expiry, sodrain --waitordrain --cancel-queuedcan never shorten it. It needs no daemon unless a cancel flag or--waitis used, and it prints the expiry. If the daemon is down or lost for one of those flags, the error says intake stays closed until the expiry and how to reopen it. A queued or running job continues exactly as before, at the normal limit. A pause stays independent of a drain.--cancel-queuedcancels, in one pass inside the daemon, every job and lease that has not started; their callers exit 130. A job that has started is never touched by it, even if it started a moment before. With both cancel flags the queued pass runs first, so no queued job starts in the place of one you cancel.--cancel-runningcancels running jobs and releases held leases, and alone it leaves queued jobs in place, which then start as slots free up; add--cancel-queuedto cancel those too. Neither is ever implied.--waitreturns 0 once every job is done and its cleanup has finished. It prints progress about every 15 s, names a held lease that blocks it (release it or use--cancel-running) and says when the queue is paused, because queued jobs then do not start untilagent-heavy resumeorundrain. If the drain expires first it exits 1 and says intake has reopened. On a signal it exits 128 plus the signal and leaves the drain in place.undrainreopens intake. With an active drain and a running daemon it resumes the queue first and removes the drain afterwards (if the resume fails the drain stays). That also ends a pause you set withagent-heavy pauseor the pause after an unclean restart, and it says so when the queue was paused. With an active drain and no daemon it keeps the drain, exits 1 and tells you to open the app, wait until the queue is up and run it again, so that waiting callers stay attached;undrain --forcedrops the drain anyway and those callers then exit 125. With no active drain (none, expired or invalid file) it removes any stale file and changes nothing else, and in particular does not resume a paused queue.
While a drain is active a new agent-heavy run follows the caller's --on-drain choice:
--on-drain |
Behaviour |
|---|---|
wait (default) |
No job is created. The caller prints a notice every 15 s and waits until the drain ends, then submits normally if the queue is running. If the drain expires while the queue is paused the caller exits 125 with the usual paused-queue message, so run undrain before the drain expires. |
fail |
Exits 75 at once. No job is created, nothing is left behind. |
direct |
Runs the command in the caller, outside the queue, once, with stdin EOF, the same environment filtering and Go resource defaults, and nesting still refused. Refused with exit 2 for a non-default --lane, because lanes protect exclusive resources. |
agent-heavy lane acquire (including --no-wait) is refused with exit 1 during a drain. agent-heavy status shows Queue running, draining until HH:MM and status --json adds queue.drainUntil. If the drain file is invalid or unsafe, intake stays closed (callers fail closed with exit 125), the status line says intake closed by an invalid drain file and queue.drainError carries the reason; agent-heavy undrain removes it.
While a drain is active, a caller or a worker waiting for the shared slot does not exit 125 when the daemon disappears; it keeps waiting and returns the job's real exit status once a daemon is back, even if the job finished meanwhile. A queued job stays attached. It starts after undrain if the restarted queue came up paused, as it does after an unclean quit; if it did not come up paused, queued jobs start as soon as the daemon is back, drain or not. A drain does not spend the caller's outage budget (without a drain the caller gives up after about two minutes, as described above); if the drain ends during an outage, the ordinary budget starts at that moment. A signal to such a caller detaches it (exit 128 plus the signal) as soon as the queue call it is in returns, which is at once with a daemon that is away and up to about 15 s with a hung one (a caller still submitting can need about 25 s, because it may first wait for the scheduling lock). It never cancels the job, and the job itself keeps running or stays queued: it prints job N stays in the queue, use agent-heavy cancel N (a running job keeps running). If a cancel had already been sent and its reply was lost, it prints job N may still be running; check agent-heavy status instead. If the queue store already holds the job's result, the caller reports that result and its output rather than the interrupt. When the job already finished and only its cleanup is unconfirmed, the signal ends the wait and the caller reports the job's own exit status with a note, as it does for any unconfirmed cleanup. The same applies if the daemon disappears just as the signal arrives. A run whose earlier submit was lost with the daemon says submission outcome unknown instead of no job created yet, and a signal then tells you the job may still exist, so check agent-heavy status. A --on-drain direct command runs in its own session and process group, so signals (SIGINT, SIGTERM, SIGHUP and SIGQUIT) reach its descendants and it has no controlling terminal. drain --wait and drain --cancel-running refuse to run inside a queued command, because they would wait for or cancel their own job. While the daemon is away during a drain, a waiting run whose submit may have been committed keeps retrying the same request: if the daemon then knows the job it attaches to it, and if it refuses because of the drain the caller goes back to the ordinary wait. After a drain expires while the daemon is away, a worker still waiting for the shared slot exits 125 without running its command, and that 125 is recorded as the job's outcome, as it is without a drain. A daemon and callers from before this feature ignore the drain, so it applies from the upgrade after the one that ships it. Once a drain expires, a lost daemon is handled by the ordinary budget: about two minutes of retries, then status 125 unless the queue store shows the job finished.
Known limitations that the drain does not change:
- If a job is killed with SIGKILL while its shared validation slot is being torn down, the slot can be left half removed. A caller attached across a daemon gap can then exit 125, and later jobs can fail until the slot is cleared by hand.
- In adaptive mode, a job started with Start now that is still waiting for the shared validation slot after its admission grant expires can wait until it is cancelled. This is existing behaviour.
The queue limits how many wrapped commands can run. A command may still create many workers or use substantial memory, so configure worker limits within your build and test tools too. There is no host memory quota, and persistent agent processes are not controlled by this queue.
Plugins add lanes: named queues with their own concurrency, priority and share of the parallel limit. A plugin is a declarative manifest in ~/.config/agent-heavy/plugins/<name>/plugin.json; it runs no code inside the queue. Optional hooks are separate, time-limited processes started on queue events. Without plugins, the queue behaves exactly as before in its default lane.
agent-heavy plugins list
agent-heavy run --lane qa -- npm run e2e
agent-heavy lane acquire qa --owner-pid "$PPID" --label "QA session" --timeout 60
agent-heavy lane release 42 --owner-pid "$PPID"A lease holds a lane slot without a command, for multi-step work such as driving an emulator across many agent tool calls. It ends when released, when its owner process exits, or when its optional TTL passes. See plugins and lanes for the manifest, scheduling rules, lease commands, status fields and hooks.
The app also starts and supervises a resource controller (agent-heavy scheduler), restarting it whenever it exits. It samples host CPU, memory pressure, available memory, swap activity and disk capacity, and measures the owned workers of running jobs. Job details show predicted memory and CPU demand, observed memory peaks, CPU-seconds, sample counts and confidence. Monitoring keeps working when the queue window or the submitting terminal closes, and runs for as long as the app does.
The default Fixed mode preserves the existing parallel limit while collecting history. Learn only makes shadow admission decisions without controlling starts. Adaptive holds waiting jobs until predicted CPU and memory demand fit the current headroom, within the same operator-set parallel limit. Choose the mode in the menu app or dashboard, or use:
agent-heavy scheduling shadow
agent-heavy status
agent-heavy limit 2
agent-heavy scheduling adaptive
agent-heavy scheduling fixedChanging the limit is an explicit operator choice. Adaptive mode cannot increase it. It reserves room for later job peaks, waits during memory pressure or active swapping, and requires a recovery interval before starting more work. Unfamiliar workloads run alone until comparable successful measurements accumulate. If resource telemetry is unavailable or stale, adaptive jobs remain waiting with an explanation. Fixed and learning modes remain available. Returning to Fixed returns waiting jobs to ordinary queue scheduling without stopping active jobs. Start now is an explicit override of resource admission and the parallel limit.
Worktrees of the same repository share a logical workload family, using repository-relative task and target identity rather than checkout paths or branch names. Changed source revisions, scripts, dependencies, toolchains and worker settings create variants that cautiously borrow the family's history. New worktrees can have cold caches; their timing is not assumed to match a warm checkout. For opaque commands, an optional logical name can relate comparable jobs deliberately:
AGENT_HEAVY_PROFILE=project-validation agent-heavy -- bash scripts/validate.shUse the same explicit profile only for related workloads. Their meaningful arguments and settings still distinguish variants. Do not put credentials in profile names. History stores opaque identities and numeric observations, with bounded retention; raw environment and command contents are not copied into the history database.
Choose Learned workloads in the app's gear menu or phone navigation to inspect measured peaks, variants, confidence and recent runs. Rename profiles, share learning between similar job families, separate a family, set minimum RAM/CPU reservations, or reset obsolete measurements. Saved corrections affect future estimates without stopping active jobs. The CLI exposes the same controls through agent-heavy profiles; see Inspect and correct workload learning for details.
Measurements are sampled estimates, not enforced CPU or memory quotas. Workers and peaks entirely between samples can be missed. Failed, cancelled, interrupted and incompletely observed runs cannot establish that a workload is cheap, while their observed peaks still raise conservative estimates. Adaptive mode does not suspend or terminate jobs, control unrelated apps, or rewrite tool flags. See the scheduling contract for learning, ownership and recovery details.
Remote access starts off. In the menu-bar gear menu, open Remote access, set a password of at least four characters, and choose Save password and enable. A short word or PIN works, with no character-mix requirement. The username is agent.
The settings window displays the HTTPS address and certificate sharing controls:
- Transfer the public
root.crtcertificate to your phone. - On Android, install it as a CA certificate in the system's certificate settings. Settings names vary by device; search for "CA certificate". This certificate is for HTTPS.
- Open the displayed address in your browser and sign in. Away from home, connect a VPN that routes to the Mac's home LAN. The Mac must be awake and logged in.
The dashboard shows the queue, details and logs, and supports reordering, concurrency changes, starting, and cancelling jobs. Connection failures preserve the last data and disable mutations until a successful refresh. No router forwarding or public hosting is configured.
Caddy's Go modules provide HTTPS and Basic authentication inside the remote server process. Only a bcrypt hash is stored. Gorilla CSRF protects mutations, and the Go HTTP server serves a private Unix-socket interface with a fixed queue-control allowlist. Existing passwords and certificate trust survive the embedded gateway upgrade. The menu bar app runs the remote service as its own child process, so phone access is reachable only while the app is running. The app starts it at launch while access is enabled and restarts it after a failure; agent-remote enable only records the choice: the app starts the service when you enable access in its settings, and at its next launch. Disabling access stops only the remote service. Quitting the app stops it, and it starts again at the next launch. A failed stop remains visible as Needs attention.
Remote authentication admission defaults to two concurrent checks, four requests per second per socket-peer IP, a burst of 64, and at most 1,024 peer entries expiring after 600 idle seconds. Cached dashboard polls count toward the rate. A bounded queue of at most burst requests accommodates initial asset requests; excess work receives HTTP 429 with Retry-After. Forwarded headers cannot choose the rate-limit identity. The existing bcrypt cost, Caddy cache, TLS, and CSRF protections remain in place.
Advanced automation can include this optional object in the password configuration JSON passed privately on stdin to agent-remote configure:
"auth_admission": {
"max_concurrent": 2,
"requests_per_second": 4,
"burst": 64,
"max_peers": 1024,
"peer_ttl_seconds": 600
}The configuration input still requires password and accepts an optional port; never put the password in command-line arguments. All five admission fields are required when the object is supplied. Allowed ranges are max_concurrent 1-32, requests_per_second 0.1-100, burst 1-512, max_peers 1-65,536, and peer_ttl_seconds 1-86,400. The peer TTL must also be at least burst / requests_per_second, preventing expiry from restoring a burst before normal refill. Omitting the object preserves existing custom limits, including when the native form saves a password; an unconfigured installation uses the defaults. agent-remote status --json reports the effective limits as authAdmission. Configuration changes to enabled access use the existing ownership-checked service restart.
The installation uses these intentional per-user locations:
| Purpose | Location |
|---|---|
| App (everything runs from inside it) | /Applications/Agent Queue.app or ~/Applications/Agent Queue.app |
| CLI commands (wrappers that call the app's bundled tools) | ~/.local/bin/agent-heavy, ~/.local/bin/agent-remote |
Installation markers (installation.json, build-info.json), no binaries |
~/.local/share/agent-heavy/ |
| Private configuration | ~/.config/agent-heavy/ |
| Private jobs, receipts, logs and daemon state | ~/.local/state/agent-heavy/ |
| Queue processes | Children of the menu bar app (agent-heavy daemon, agent-heavy scheduler, and agent-remote serve when phone access is on), logging to daemon.log, resources.log and remote/service.log under ~/.local/state/agent-heavy/, each bounded to 1 MiB when the process starts |
| Login item | The app's own login item, registered with Service Management (open at login) |
No executables are copied out of the app bundle, and new installs write nothing to ~/Library/LaunchAgents. When migrating from an earlier installer-based install, setup can retire and back up legacy io.went.agent-*.plist definitions found there. Setup also unregisters queue, resource and phone-access registrations from earlier app versions; Agent Queue.app/Contents/Library/LaunchAgents still holds their definitions in the bundle so that upgrades can do this.
The dedicated Go daemon uses a private Unix socket and a durable SQLite database. The app restarts it after a failure, adopts a daemon that is already running instead of starting a second one, and the daemon's own lifetime locks refuse duplicate instances. Restart policy: the daemon and resource controller restart after any exit while the app runs; the phone-access service restarts after a failure or while its settings remain enabled, and a restart waits until 10 seconds after the previous start. The queue fails closed when its daemon, state, or process ownership cannot be verified. Do not clear a lock or kill a process to bypass an unknown owner.
After an unclean daemon restart, including quitting the app while jobs are active, automatic scheduling remains paused while owned workers and their outcomes are reconciled. Inspect agent-heavy status, then choose Resume in the app’s paused banner or use agent-heavy resume to resume the queue. agent-heavy pause holds future automatic starts without stopping active jobs. An uncertain command attempt is never automatically replayed.
agent-remote status --json reports the remote service. agent-remote disable stops it. Passwords are passed privately through the native settings form, not command-line arguments.
For an optional migration bridge, AGENT_HEAVY_LEGACY_SLOT selects an existing external lease directory; otherwise the queue uses its private execution lease. AGENT_HEAVY_STATE selects an alternative private queue state directory. AGENT_HEAVY_CONFIG retains the legacy configuration location for migration. Set overrides consistently for both submitters and the daemon environment.
See CONTRIBUTING.md for local commands and the CI workflow for exact hosted checks. The separate distribution workflow signs, notarizes, verifies and publishes retained main-build artifacts.
Pull requests run Go formatting, vet, vulnerability scanning and race tests, JavaScript lint, shell and workflow checks, isolated Go daemon acceptance tests, embedded Caddy HTTPS/API tests, browser regression tests, native Swift regression tests, and universal app and disk image verification. The workflow runs macOS behavior on Apple Silicon and Intel runners. All validation jobs feed one required ci-required check; a failure, cancellation, or skipped required job fails that check.
main requires a pull request, current passing checks, resolved review conversations, and verified commit signatures. Force pushes and deletion are blocked, including for administrators. Commits also require an author-matching DCO sign-off. The initial repository import bootstraps the branch; later changes use pull requests.
Distribution requires a successful, verified main CI build. A manual distribution run can use corrected main automation with that same build's retained artifacts and notarization checkpoints, preserving its original commit, version and build number. See Apple signing and notarization for recovery. Pull request code receives read-only repository permissions. Actions are pinned to immutable commit SHAs, npm dependencies use a lockfile, and Go dependencies are pinned in go.mod and go.sum.
MIT. See third-party notices for bundled Go modules, Zod, and separately installed dependencies.



