Open-source CI wiring for PR description-driven agent verification.
This repository packages two pieces:
- A small HTTP runner that receives GitHub pull request jobs, checks out the PR, builds a verification prompt, runs
codex exec, and stores pollable results. - A reusable GitHub Actions workflow plus bootstrap script that sends the live PR title/body to the runner and lets each PR declare scope in a managed PR description block.
Linked Backlog and Linear sources remain authoritative. The PR description block is the operator-authored scope summary for one PR. If a linked source cannot be resolved, or if linked sources conflict with the PR summary, the runner verdict is needs-human.
Repository Layout
src/: runner service implementationschemas/: structured verifier output schemaexamples/github-actions-pr-verify.yml: consumer repository workflowtools/setup_scope_ci.py: consumer repository bootstrap scriptskill/: Codex skill packaging for agents that install workflows into reposdeploy/: generic deployment examplesdocs/: workflow and API contracts
Runner Requirements
- Node.js 22 or newer
- Git
- Codex CLI available on
PATH - Network access from the runner host to GitHub
- SSH access from the runner host to the GitHub repositories it verifies
Run Locally
cp .env.example .env # edit RUNNER_SHARED_SECRET before exposing the service npm test npm start
The runner listens on RUNNER_BIND_HOST:RUNNER_PORT, defaulting to 127.0.0.1:30450.
Configure A Repository For Verification
From this repository, run:
python3 tools/setup_scope_ci.py --repo /path/to/consumer/repo
The script writes:
.github/workflows/codex-pr-verify.yml.github/pull_request_template.md
It also removes old temporary-model managed files if present:
.github/workflows/codex-scope-verify.yml.github/codex/active-scope.json.github/scripts/cleanup-codex-scope.sh
Configure these GitHub Actions secrets in the consumer repository:
CODEX_RUNNER_URL: public URL for this runnerCODEX_RUNNER_BASIC_AUTH: optionaluser:passwordfor edge basic auth; leave unset when the runner URL does not use basic authCODEX_RUNNER_WEBHOOK_SECRET: same value as runnerRUNNER_SHARED_SECRET
PR Scope Block
Each PR should contain exactly one managed block:
<!-- codex-runner-scope:start --> ## Codex Runner Scope ```json { "sources": [ "Backlog:TASK-42" ], "spec_summary": "Describe the intended scope this PR should satisfy.", "notes": [ "Optional reviewer or implementation note" ] } ``` <!-- codex-runner-scope:end -->
Supported source references:
Backlog:TASK-XLinear:ISSUE_ID- Linear issue URL
API
The workflow posts to POST /jobs/pr-verify with the live PR title/body:
{
"provider": "github",
"repo": "owner/name",
"pr_number": 123,
"base_ref": "main",
"head_ref": "feature-branch",
"prompt_profile": "pr_plan_verify",
"pr_context": {
"title": "PR title",
"body": "full current PR description markdown",
"source": "pull_request.body"
},
"metadata": {
"trigger": "pull_request",
"context_source": "github.event.pull_request.body"
}
}See docs/workflow-contract.md for the full contract.
Security Notes
- Do not expose the runner without
RUNNER_SHARED_SECRET. - Prefer HMAC request signing via
X-Codex-Runner-TimestampandX-Codex-Runner-Signature. - If you put the runner behind basic auth, the generated workflow still signs the raw request body for runner-level authentication.
- Keep
jobs/andworkspaces/private; they may contain PR source, prompts, diffs, and verifier artifacts.
Tests
The e2e test launches real Codex jobs and is gated: