Files
zero/docs/GITHUB_ACTION.md
Anandan ac6addaa83 Align sandbox permission blocks and backend states (#253)
* Prompt for shell network permissions

Route network-risk shell calls through the existing permission prompt instead of hard-denying them at sandbox preflight. Approved network prompts apply the current turn/session request-permissions overlay so the effective sandbox policy is widened only after user approval.

Move network command detection into the shell AST analyzer for package installers, gh/git network subcommands, and python http.server so quoted process patterns such as pkill -f "python3 -m http.server 8000" are not misclassified as network activity.

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/sandbox -run 'TestAnalyzeCommand|TestClassifyDoesNotFlagQuotedHttpServerPattern|TestEvaluateExemptsNetworkToolsFromDenyByDefault|TestEngineClassifiesNetworkAndDestructiveShellCommands|TestGrantRequestPermissionsNetworkOverlaysPolicyForTurn'

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/agent -run 'TestRunPersistentCommandPrefixStillPromptsForNetwork|TestRunApprovedNetworkBashPromptAppliesTurnNetworkGrant'

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/tools -run 'TestBash|TestRegistryRunsBashThroughSandboxEngine'

Tested: GOCACHE=/tmp/zero-go-cache go test ./...

* Auto-allow native-sandboxed shell commands

Remove the old hidden bash auto-allow environment/config path. Ordinary shell commands now use the active native sandbox as the permission boundary by default, while network, destructive, and workspace policy checks still run first.

Policy-only and disabled sandbox backends continue to require approval for shell commands, so unsandboxed execution is not silently allowed.

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/sandbox -run 'TestSandboxedBash|TestShellSandboxActive|TestEvaluateExemptsNetworkToolsFromDenyByDefault|TestGrantRequestPermissionsNetworkOverlaysPolicyForTurn|TestAnalyzeCommand|TestClassifyDoesNotFlagQuotedHttpServerPattern'

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/tools -run 'TestBashAuto|TestBashStillPromptsWithoutActiveSandbox|TestRegistryRunsBashThroughSandboxEngine'

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/cli -run 'TestApplyConfiguredSandboxPolicy|TestApplyConfiguredAutonomyCeiling'

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/agent -run 'TestRunPersistentCommandPrefixStillPromptsForNetwork|TestRunApprovedNetworkBashPromptAppliesTurnNetworkGrant'

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/daemon -run 'TestScrubWorkerEnvRemovesReentrancyMarkers'

Tested: GOCACHE=/tmp/zero-go-cache go test ./...

* Prompt for destructive shell approval

Remove the separate DenyDestructiveShell policy layer from active sandbox decisions. Destructive shell classification now produces an approval prompt in ask/auto flows instead of a sandbox hard-deny, while no-approval paths remain blocked.

Drop the removed destructive-shell policy field from sandbox policy output, backend capabilities, snapshots, and Windows golden data.

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/agent -run 'TestRunPromptsForDestructiveShellInsteadOfSandboxDeny|TestRunPersistentCommandPrefixStillPromptsForNetwork|TestRunApprovedNetworkBashPromptAppliesTurnNetworkGrant'

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/sandbox -run 'TestEngineClassifiesNetworkAndDestructiveShellCommands|TestBackendCapabilitiesReflectDisabledPolicy'

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/cli -run 'TestRunSandboxPolicy|TestEffectiveSandboxPolicy'

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/zerocommands -run 'TestSandboxPolicySnapshotFromPolicyFillsEffectiveMode'

Tested: GOCACHE=/tmp/zero-go-cache go test ./...

* Remove web-tool network policy switch

Remove the old EnforceToolNetwork policy option and the NetworkHostAllowed per-tool gate so sandbox network policy only controls sandboxed shell egress.

Keep web_search and web_fetch on their own tool permission and URL safety paths, with tests proving deny/scoped shell network policy no longer blocks in-process web tools.

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/sandbox -run 'TestEvaluateExemptsNetworkToolsFromShellNetworkPolicy|TestEngineAllowsNetworkSideEffectWhenShellPolicyBlocksNetwork|TestGrantRequestPermissionsNetworkOverlaysPolicyForTurn|TestScopedNetworkGateInEvaluate'

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/tools -run 'TestWebSearchRunWithSandbox|TestWebFetchRunWithSandbox|TestRegistryRoutesWebFetchThroughSandboxPath'

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/agent -run 'TestRunPersistentCommandPrefixStillPromptsForNetwork|TestRunApprovedNetworkBashPromptAppliesTurnNetworkGrant'

Tested: GOCACHE=/tmp/zero-go-cache go test ./...

Tested: git diff --check

* Prompt for outside-workspace file access

Convert recoverable outside-workspace file path violations into permission prompts instead of hard-denying in ask/auto modes.

Approved prompts now grant the requested filesystem scope through the existing request_permissions overlay for the current turn or session, so the follow-up sandbox evaluation and scoped file tools use the same policy path.

Keep unsafe mode, apply_patch escapes, symlink traversal, and non-recoverable path cases hard-denied.

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/sandbox -run 'TestEngineAutoAllowsWorkspaceFileMutationTools|TestEngineDeniesOutOfWorkspacePaths|TestGrantRequestPermissionsReadDoesNotGrantWrite|TestGrantRequestPermissionsSessionPersists'

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/agent -run 'TestRunPromptsAndAllowsOutsideWorkspaceWrite|TestRunSessionAllowsLaterOutsideWorkspaceWrite|TestRunAppliesSandboxEvenInUnsafeMode'

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/tools -run 'TestRegistryAppliesSandboxBeforeToolExecution|TestRegistrySandboxGatesPathAliasKeys|TestRegistryAllowsPromptToolWithPersistentSandboxGrant'

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/cli -run 'TestRunSandboxCheckJSONDeniesOutOfWorkspaceWrite'

Tested: GOCACHE=/tmp/zero-go-cache go test ./...

Tested: git diff --check

* Fix Windows network prompt test fixture

Use a Windows curl.cmd shim and cmd.exe PATH syntax for TestRunApprovedNetworkBashPromptAppliesTurnNetworkGrant while keeping the POSIX fake curl fixture for Linux and macOS.

This keeps the test focused on network prompt approval and turn network grants instead of relying on a POSIX shell script being executable on Windows.

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/agent -run 'TestRunApprovedNetworkBashPromptAppliesTurnNetworkGrant|TestRunPersistentCommandPrefixStillPromptsForNetwork'

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/sandbox -run 'TestClassifyASTCatchesNetworkProgramsRegexMisses|TestClassifyFlagsPipedInstallerVariants'

Tested: GOCACHE=/tmp/zero-go-cache go test ./...

Tested: git diff --check

* Remove sandbox autonomy ceilings

Sandbox permission decisions now match grants by tool and scope only, without MaxAutonomy or request autonomy checks. Persistent/session grants, permission approvals, workspace-write auto-allow, and native-sandbox shell auto-allow no longer route through an autonomy ceiling.

Remove the sandbox.maxAutonomy config and CLI surfaces, including grant --auto, policy output, sandbox check autonomy input, and sandbox grant snapshots.

MCP permission autonomy is intentionally unchanged because it is a separate MCP permission model.

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/sandbox ./internal/agent ./internal/tools ./internal/zerocommands ./internal/config ./internal/cli ./internal/tui

Tested: GOCACHE=/tmp/zero-go-cache go test ./...

* Fail closed without native sandbox

Remove AllowPolicyOnlyRunner from sandbox policy and snapshots. A restricted/managed command plan now errors when native sandbox support is unavailable instead of running through a policy-only direct runner.

Policy-only backend data remains as diagnostics for capability/reporting paths, but it no longer authorizes command execution when a platform sandbox is required.

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/sandbox ./internal/zerocommands ./internal/cli ./internal/tui

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/agent ./internal/tools

Tested: GOCACHE=/tmp/zero-go-cache go test ./...

* Remove scoped shell network policy

Drop the domain-scoped shell network mode and the local scoped-egress proxy path from the active sandbox model.

Shell network policy now serializes and evaluates only deny or allow. Request-permissions network grants widen to allow without domain lists, and platform profiles consume the binary mode directly.

The Linux helper no longer accepts proxy-route flags, Windows network hashes only the binary mode, and web tool tests assert they remain separate from shell network policy.

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/sandbox ./internal/tools ./internal/notify

Tested: GOCACHE=/tmp/zero-go-cache go test ./...

* Align permission grant ordering and prompt text

Move persistent and session grant matching ahead of recoverable sandbox prompts while keeping explicit denies and non-recoverable path violations fail-closed.

Clean TUI permission transcript/card text so normal prompts show action, reason, scope, and readable block details instead of raw permission/risk/violation key-value metadata.

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/sandbox ./internal/tui ./internal/agent ./internal/tools

Tested: GOCACHE=/tmp/zero-go-cache go test ./...

* Remove policy-only sandbox terminology

Replace the unavailable native-backend diagnostic path with explicit unavailable support/backend states, and stop presenting missing native isolation as a policy-only fallback.

Rename structured sandbox denial metadata from violation to block across permission events, stream-json, snapshots, TUI rendering, and sandbox-check output so user-facing flow matches blocked action language.

Update README, work split docs, smoke test selectors, and Windows sandbox policy golden output for the new backend/support names and block payloads.

Tested: GOCACHE=/tmp/zero-go-cache go test ./internal/sandbox ./internal/agent ./internal/tools ./internal/tui ./internal/cli ./internal/doctor ./internal/zerocommands ./internal/streamjson

Tested: GOCACHE=/tmp/zero-go-cache go test ./...

Tested: scripts/sandbox-smoke.sh

Tested: git diff --check

* Fix sandbox review follow-ups

Fail closed for unknown network modes, keep network prompts ahead of bash allow grants, preserve v1 grant-file reads, and classify unparseable commands with obvious network programs as network-risk.

Bump stream-json schema to v2 and document the block metadata field after the violation-to-block rename.

Tests: GOCACHE=/tmp/zero-go-cache go test ./internal/sandbox ./internal/streamjson ./internal/specialist ./internal/cli -run 'TestExec.*Stream|TestStreamJSON|TestExecProtocol|TestExecReadsStreamJSONImages|TestExecAcceptsImageOnlyStreamJSONMessage|TestExecInputFormatStreamJSON'

Tests: GOCACHE=/tmp/zero-go-cache go test ./...

Tests: scripts/sandbox-smoke.sh

Tests: git diff --check
2026-06-19 19:50:12 +05:30

7.9 KiB

ZERO GitHub Action

Run ZERO headlessly inside a GitHub workflow. The action is a thin wrapper around zero exec: it installs a pinned ZERO release, runs your prompt in the checked-out repository, surfaces ZERO's exit code as the step status, captures the output as a file, and can optionally post a summary to the triggering pull request or to Slack.

ZERO is model- and provider-agnostic, and so is this action: you choose the provider and supply the API key. Nothing is hardcoded to any single provider.

Quick start

# .github/workflows/zero.yml
name: ZERO
on:
  workflow_dispatch:
    inputs:
      task:
        description: What should ZERO do?
        required: true

permissions:
  contents: write

jobs:
  run:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: Gitlawb/zero@v1
        with:
          prompt: ${{ inputs.task }}
          provider: openai
          api-key-env: OPENAI_API_KEY
          api-key: ${{ secrets.OPENAI_API_KEY }}
          model: gpt-4.1

The provider, api-key-env, and api-key trio is how you stay provider-neutral: api-key-env is the environment variable name the chosen provider reads its key from (for example ANTHROPIC_API_KEY, GEMINI_API_KEY, OPENROUTER_API_KEY), and api-key is the secret value. The action exports that variable for the run and never prints it. If your repository already commits a .zero/config.json with an active provider, you can omit provider.

Inputs

Input Required Default Description
prompt one of prompt/prompt-file "" The instruction for ZERO to execute.
prompt-file one of prompt/prompt-file "" Path (relative to working-directory) to a file whose contents are the prompt.
provider no "" Provider id to activate (e.g. openai, anthropic, gemini, ollama, or any compatible endpoint).
api-key no "" The provider API key. Pass from a secret; never logged.
api-key-env no "" Env var name the provider reads its key from (e.g. OPENAI_API_KEY). Exported with api-key.
model no "" Model id. Defaults to the resolved provider's default.
mode no "" Run mode (zero exec --mode), e.g. smart, deep, fast.
auto no low Autonomy ceiling (zero exec --auto): low, medium, or high. Conservative by default.
self-correct no false Allow mid-run model escalation (zero exec --allow-escalation).
add-dir no "" Newline-/comma-separated extra write roots (zero exec --add-dir).
worktree no false Run in an isolated git worktree (zero exec --worktree).
output-format no stream-json text, json, or stream-json. Captured to a file.
post-to no none pr-comment, slack, or none. Where to post a summary after the run.
slack-webhook-url no "" Slack incoming-webhook (or generic webhook) URL for post-to: slack. Pass from a secret.
github-token no ${{ github.token }} Token used to post a PR comment. Requires pull-requests: write.
working-directory no ${{ github.workspace }} Directory to run ZERO in.
zero-version no (action ref → latest) ZERO release version/tag to install, e.g. v1.2.3 or latest.
zero-repo no Gitlawb/zero Repository to install the ZERO release from.

Outputs

Output Description
exit-code ZERO's exit code (0 success, 2 usage, 3 provider, non-zero otherwise).
output-file Path to the captured ZERO stdout (the raw output-format stream).
summary A short, single-line summary parsed from the run, when available.

The step fails when ZERO returns a non-zero exit code, so a failed run fails the job by default. Use continue-on-error: true on the step (and read exit-code) if you want to handle failures yourself.

Examples

Run ZERO on every issue labeled zero

name: ZERO issue triage
on:
  issues:
    types: [labeled]

permissions:
  contents: write
  issues: write
  pull-requests: write

jobs:
  triage:
    if: github.event.label.name == 'zero'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: Gitlawb/zero@v1
        with:
          prompt: |
            Investigate this issue and propose a fix.

            Title: ${{ github.event.issue.title }}

            ${{ github.event.issue.body }}
          provider: anthropic
          api-key-env: ANTHROPIC_API_KEY
          api-key: ${{ secrets.ANTHROPIC_API_KEY }}
          auto: low
          post-to: slack
          slack-webhook-url: ${{ secrets.ZERO_SLACK_WEBHOOK_URL }}

Nightly dependency-upgrade PR

name: ZERO nightly deps
on:
  schedule:
    - cron: "0 6 * * 1" # Mondays 06:00 UTC

permissions:
  contents: write
  pull-requests: write

jobs:
  deps:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: Gitlawb/zero@v1
        id: zero
        with:
          prompt-file: .github/zero/upgrade-deps.md
          provider: openai
          api-key-env: OPENAI_API_KEY
          api-key: ${{ secrets.OPENAI_API_KEY }}
          worktree: true
          auto: medium
      - name: Open pull request
        run: |
          git checkout -b zero/deps-$(date +%Y%m%d)
          git commit -am "chore(deps): nightly upgrade via ZERO" || exit 0
          git push -u origin HEAD
          gh pr create --fill --label dependencies
        env:
          GH_TOKEN: ${{ github.token }}
      - name: Upload ZERO output
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: zero-output
          path: ${{ steps.zero.outputs.output-file }}
          if-no-files-found: warn

Security notes

  • Secrets are passed as Action secrets and never logged. The action exports api-key (under api-key-env) and slack-webhook-url into the step environment only. They are never echoed, and ZERO redacts secret-shaped strings from its own output.
  • The sandbox is always active. This action never passes --skip-permissions-unsafe, so writes stay inside the checked-out repository (plus any roots you grant with add-dir). Unsafe mode is never enabled implicitly.
  • Autonomy defaults to low. Raise it deliberately (auto: medium/high) only for tasks you trust to run unattended.
  • Least-privilege tokens. Grant only the permissions the workflow needs (contents: write to edit files, pull-requests: write to comment). The default GITHUB_TOKEN is scoped to the repository.
  • Pin the action. Reference a tag (Gitlawb/zero@v1) or a commit SHA so a workflow run uses a known ZERO version. zero-version lets you pin the installed binary independently of the action ref.
  • Linux and macOS runners are supported; Windows runners are rejected with a clear error.

Slack / webhook notifier

The action's post-to: slack step sends a one-line summary to a Slack incoming webhook after the run. ZERO also has a built-in webhook notifier sink (internal/notify) that an unattended run can use to report "finished / needs input / verify failed after N retries" to Slack or any generic webhook:

  • Configure the destination with the ZERO_SLACK_WEBHOOK_URL environment variable (or settings). A blank URL disables the sink.
  • The sink POSTs a JSON body { "text", "type", "message", "summary?", "links?" }. The text field is what Slack renders; the structured fields carry the machine-readable detail.
  • Fail-soft: a non-2xx response or a transport error is logged (redacted) and swallowed — a webhook problem never crashes the run.
  • Redaction: the message, summary, links, and the webhook URL itself are run through ZERO's redaction before being sent or logged, so tokens never leak.
  • Egress/proxy: the notifier uses the default HTTP transport, which honors HTTP_PROXY/HTTPS_PROXY when a proxy is configured.