Local agent runner that queues and executes GitHub Agent requests using Codex.
- Accepts requests from GitHub Issues / PRs via an issue comment:
/agent run(recommended with webhooks). - Queues requests, runs up to the configured concurrency, and posts results back to GitHub.
- Runs each request in an isolated git worktree under
workdirRoot/agent-runner/work/to avoid mixing changes across concurrent runs. - Runs idle maintenance tasks when no queued issues are available.
- Can optionally run idle tasks through Copilot/Gemini/Amazon Q/Claude when configured usage gates allow.
- Designed for a self-hosted Windows machine running Codex CLI.
- Install dependencies.
npm install- Ensure Codex CLI is available in PATH.
codex --version-
If you want Copilot idle runs, ensure the configured Copilot command is available in PATH.
-
If you want Gemini or Amazon Q idle runs, ensure their configured commands are available.
-
Create a GitHub token with repo access and set it as an environment variable.
setx AGENT_GITHUB_TOKEN "<token>"- If using webhooks, set the GitHub App webhook secret.
setx AGENT_GITHUB_WEBHOOK_SECRET "<secret>"- If using Cloudflare Tunnel, set the tunnel token for auto-start.
setx CLOUDFLARED_TUNNEL_TOKEN "<token>"If the environment variable is not available (for example, before a logoff/logon),
you can also save the token to state/cloudflared-token.txt (ignored by git).
- Update
agent-runner.config.jsonwith your workspace root and concurrency.
npm run lintnpm run lint:ps(PowerShell lint via PSScriptAnalyzer; required byverify)npm run test(unit tests + local CLI checks; does not call GitHub API)npm run buildnpm run verify(lint + lint:ps + test + build)npm run dev -- run --once --yes
On an Issue or PR, add an issue comment:
/agent run
Only repo collaborators (OWNER/MEMBER/COLLABORATOR) are accepted for /agent run to avoid drive-by execution.
When the target is a PR, agent-runner treats the PR as "managed" after /agent run (and for bot-authored PRs). Managed PRs:
- Re-run the agent automatically on PR review feedback (review comments / reviews), regardless of reviewer account type.
- Classify reviewer messages as:
- actionable feedback -> run a follow-up task (
requiresEngine: true) - non-actionable/OK feedback (for example "no new comments", "usage limit reached / unable to review") -> approval-style follow-up (
requiresEngine: false)
- actionable feedback -> run a follow-up task (
- Auto-merge when all known reviewers are in an OK state (no pending reviewer feedback, no actionable comments/changes requested), then best-effort delete the remote PR branch.
- After a successful review follow-up run, re-request review from all prior reviewers (human reviewers via requested-reviewers API, Copilot bot re-request, and Codex via
@codex review).
The GitHub-flow E2E suite runs against a real GitHub repository and requires environment variables. Recommended: authenticate once with GitHub CLI and use the helper script:
.\scripts\run-e2e.ps1 -Owner "<owner>" -Repo "<repo>"You can also set persistent environment variables (PowerShell):
setx E2E_GH_OWNER "<owner>"
setx E2E_GH_REPO "<repo>"
setx E2E_WORKDIR_ROOT "<path>"Then run:
npm run test:e2eEnvironment variables used by the E2E suite:
E2E_GH_OWNER: GitHub owner/orgE2E_GH_REPO: Repository nameE2E_WORKDIR_ROOT: Workdir root for temporary clones/logs (optional)AGENT_GITHUB_TOKEN(orGITHUB_TOKEN/GH_TOKEN): Token with repo access
Config file: agent-runner.config.json
owner: GitHub user or orgrepos:"all"or explicit listworkdirRoot: Local workspace root containing repospollIntervalSeconds: Polling intervalconcurrency: Max concurrent requestsserviceConcurrency: Optional per-service concurrency (defaults to 1 for each service)serviceConcurrency.codex: Max concurrent Codex executionsserviceConcurrency.copilot: Max concurrent Copilot executionsserviceConcurrency.gemini: Max concurrent Gemini executions (pro/flash combined)serviceConcurrency.amazonQ: Max concurrent Amazon Q executionsserviceConcurrency.claude: Max concurrent Claude executions
repos: If"all", the runner caches the repository list and refreshes periodically to avoid GitHub rate limits. When rate-limited and no cache is available, it will fall back to local workspace repositories (directories with a.gitfolder).logMaintenance: Optional log pruning settings (applied automatically on startup and viaagent-runner logs prune)reportMaintenance: Optional report pruning settings (applied automatically on startup and viaagent-runner reports prune)labels: Workflow labelslabels.failed: Label for execution failureslabels.needsUserReply: Label for paused runs that require a user replylabels.reviewFollowup: Label for queued managed-PR review follow-upslabels.reviewFollowupWaiting: Optional label for follow-ups waiting on idle engine gates (default:<reviewFollowup>:waiting)labels.reviewFollowupActionRequired: Optional label for follow-ups that need manual action (default:<reviewFollowup>:action-required)codex: Codex CLI command and prompt templatecodex.args: Default config runs with full access (--dangerously-bypass-approvals-and-sandbox) and pins a model (--model gpt-5.2); change this if you want approvals/sandboxing or a different model.codex.promptTemplate: The runner expects a final response block (AGENT_RUNNER_STATUS: ...+ message body) and posts that final message to the issue thread.- The default template allows GitHub operations (issues/PRs/commits/pushes) but forbids sending/posting outside GitHub unless the user explicitly approves in the issue.
- For Codex engine runs, agent-runner prepends a
$managerline to the rendered prompt to activate the localmanagerskill in Codex CLI (delegation/orchestration mode).
webhooks: Optional GitHub webhook listener configuration (recommended to avoid repo-wide polling)webhooks.enabled: Turn webhook mode on/offwebhooks.host: Host to bind for the local webhook serverwebhooks.port: Port to bind for the local webhook serverwebhooks.path: URL path for webhook requests (e.g./webhooks/github)
webhooks.secret: Webhook secret (optional if usingwebhooks.secretEnv)webhooks.secretEnv: Environment variable name holding the webhook secretwebhooks.maxPayloadBytes: Optional max payload size (bytes)webhooks.queueFile: Optional path for the webhook queue filewebhooks.catchup: Optional low-frequency fallback scan (Search API) to catch requests missed while the webhook listener was down (looks for/agent runcomments)webhooks.catchup.enabled: Turn the catch-up scan on/offwebhooks.catchup.intervalMinutes: Minimum minutes between scans
webhooks.catchup.maxIssuesPerRun: Maximum issues to queue per scancopilot: Copilot CLI command and args for idle runs (the prompt is appended as the last argument).copilot.args: Ensure-pis the final argument so the prompt is passed as the value. Use--allow-allfor non-interactive runs.
gemini: Gemini CLI command and args for idle runs (the prompt is appended as the last argument).gemini.args: Ensure-pis the final argument so the prompt is passed as the value. For non-interactive runs, include--approval-mode yoloand--allowed-tools run_shell_command,...so shell/file tools are not denied by policy.- Note: when spawning Gemini CLI, agent-runner sets
GEMINI_CLI_SYSTEM_DEFAULTS_PATHto disabletools.shell.enableInteractiveShell(avoids Windows node-pty/ConPTY noise likeAttachConsole failed).
amazonQ: Optional Amazon Q CLI command and args for idle runs.amazonQ.promptMode: Use"arg"when the prompt is passed as the last argument (recommended for WSL wrapper scripts).
claude: Optional Claude CLI command and args for idle runs (and engine-required managed PR review follow-ups).claude.args: Ensure-pis included so the prompt is passed as the final argument value.
idle: Optional idle task settings (runs when no queued issues exist)idle.enabled: Turn idle tasks on/offidle.maxRunsPerCycle: Max idle tasks per cycleidle.cooldownMinutes: Per-repo cooldown between idle runsidle.tasks: List of task prompts to rotate through
idle.promptTemplate: Prompt template for idle runs; supports{{repo}},{{task}},{{openPrCount}}, and{{openPrContext}}{{openPrCount}}is the repository's current open-PR total when available (unknownif count lookup fails).{{openPrContext}}is injected as an untrusted context block wrapped byAGENT_RUNNER_OPEN_PR_CONTEXT_START/END.
idle.repoScope:"all"(default) or"local"to restrict idle tasks to repos under the workspace rootidle.usageGate: Optional Codex usage guard (reads local session JSONL when available; otherwise queries the backend usage API viaauth.jsonin the Codex home directory)idle.usageGate.enabled: Turn usage gating on/offidle.usageGate.codexHome: Optional override for Codex home directory (used for credential discovery)idle.usageGate.timeoutSeconds: Timeout for backend usage lookupidle.usageGate.minRemainingPercent: Minimum remaining percent for the 5h windowidle.usageGate.weeklySchedule: Weekly ramp for remaining percentidle.usageGate.weeklySchedule.startMinutes: When to begin idle runs (minutes before weekly reset)idle.usageGate.weeklySchedule.minRemainingPercentAtStart: Required weekly percent left at startMinutesidle.usageGate.weeklySchedule.minRemainingPercentAtEnd: Required weekly percent left at reset time
idle.copilotUsageGate: Optional Copilot monthly usage guard (readscopilot_internal/user)idle.copilotUsageGate.enabled: Turn Copilot usage gating on/offidle.copilotUsageGate.timeoutSeconds: Timeout for Copilot usage lookupidle.copilotUsageGate.apiBaseUrl: Optional GitHub API base URL (defaulthttps://api.github.com)idle.copilotUsageGate.apiVersion: Optional GitHub API version header (default2025-05-01)idle.copilotUsageGate.monthlySchedule: Monthly ramp for remaining percentidle.copilotUsageGate.monthlySchedule.startMinutes: When to begin idle runs (minutes before monthly reset)idle.copilotUsageGate.monthlySchedule.minRemainingPercentAtStart: Required monthly percent left at startMinutesidle.copilotUsageGate.monthlySchedule.minRemainingPercentAtEnd: Required monthly percent left at reset time
idle.geminiUsageGate: Optional Gemini usage guard (reads local Gemini CLI config + Google OAuth tokens)idle.geminiUsageGate.enabled: Turn Gemini usage gating on/offidle.geminiUsageGate.startMinutes: When to begin idle runs (minutes before reset)idle.geminiUsageGate.minRemainingPercentAtStart: Required remaining percent at startMinutesidle.geminiUsageGate.minRemainingPercentAtEnd: Required remaining percent at reset timeidle.geminiUsageGate.warmup: Optional warmup run when Gemini is at 100% and the reset countdown appears stuck (default enabled)idle.geminiUsageGate.warmup.enabled: Enable/disable warmup (defaulttrue)idle.geminiUsageGate.warmup.cooldownMinutes: Minimum minutes between warmup attempts (default60)
idle.amazonQUsageGate: Optional Amazon Q monthly usage guard (runner-local tracking)idle.amazonQUsageGate.enabled: Turn Amazon Q usage gating on/offidle.amazonQUsageGate.monthlyLimit: Monthly request limit to enforce for runner-driven usageidle.amazonQUsageGate.monthlySchedule: Monthly ramp for remaining percent (same semantics as Copilot)idle.claudeUsageGate: Optional Claude usage guard (delegated to@metyatech/ai-quota)
This repo includes an optional, local-only log UI and query toolchain based on Grafana + Loki + Promtail. It avoids relying on log file naming/date boundaries (UTC vs local time) by querying logs by time range.
agent-runnerwrites logs to./logs/*.log(local files).- Promtail tails those files and ships log lines to Loki.
- Loki stores and indexes logs for fast time-range queries.
- Grafana is the web UI that queries Loki.
Why keep ./logs/*.log?
- Promtail needs a local input source to tail without adding custom log-shipping code to the runner.
- It also provides a fallback when Docker/Loki is down (you still have raw files).
Start the stack (local only):
docker compose -p agent-runner-logging -f ops/logging/docker-compose.yml up -dOpen Grafana:
http://127.0.0.1:3000(default credentials areadmin/adminon first run)
Dashboard (recommended):
- Go to
Dashboards→Agent Runner→Agent Runner Logs - Use the
Kinddropdown to switch between:task-run,task-metawebhook-run,webhook-meta- (and other kinds)
- Optional: type a substring into
Containsto filter without writing LogQL
Explore logs (examples / fallback):
- All logs:
{job="agent-runner"} - Task run logs:
{job="agent-runner", kind="task-run"} - Task meta logs:
{job="agent-runner", kind="task-meta"} - Webhook logs:
{job="agent-runner", kind="webhook-run"} - Webhook meta logs:
{job="agent-runner", kind="webhook-meta"}
Timezone note:
- Grafana shows timestamps in your browser's local timezone by default (per-user setting).
- Log lines are written with a UTC RFC3339 timestamp prefix; Promtail parses it so Loki stores the correct event timestamp.
CLI export to a file (use your own local time offset in the range):
docker run --rm --network agent-runner-logging_default grafana/logcli:3.6.0 \
query --addr http://loki:3100 \
--from "<FROM_RFC3339>" --to "<TO_RFC3339>" \
'{job="agent-runner", kind="task-run"}' > exported.logStop the stack:
docker compose -p agent-runner-logging -f ops/logging/docker-compose.yml downOne-shot execution:
node dist/cli.js run --once --yesLooping daemon:
node dist/cli.js run --yesWebhook listener (GitHub App):
node dist/cli.js webhook --config agent-runner.config.jsonWhen webhooks.enabled is true, repo-wide issue polling is skipped and the runner
relies on webhook-queued issues. Keep the webhook listener running (for example,
as a background service or scheduled task).
If webhooks.catchup.enabled is true, the runner also performs a low-frequency
Search API scan to catch requests created while the webhook listener was down.
When no queued issues exist, the runner can execute idle tasks defined in the config.
Each idle run writes a report under reports/ and streams the Codex output to logs/.
When changes are made, the idle prompt is expected to open a PR. The runner will
follow up on PR review feedback and can auto-merge managed PRs when all reviewers
are in an OK state.
To avoid duplicate idle work, the runner injects the repository's open PR context into each idle prompt and instructs the implementation agent to avoid work that overlaps existing open PRs.
The injected context is bounded (top recent entries + total character budget) so prompt size stays stable on active repositories.
The injected context is explicitly marked as untrusted data and must not override prompt requirements or AGENTS.md rules.
GitHub does not notify you about actions performed by your own account, so PR/issue comments created by a runner using your personal token may not generate notifications.
If you want GitHub Notifications on the PR/issue itself, run agent-runner with a separate identity so the author is not your own account.
Recommended: GitHub App (installation token auto-refresh).
- Create a GitHub App (Settings -> Developer settings -> GitHub Apps)
- Grant permissions:
- Issues: Read & write
- Pull requests: Read & write
- Install the app on the repositories you want to receive notifications for.
- Generate a private key (download the
.pemfile). - Configure agent-runner with the app credentials:
.\scripts\set-notify-app.ps1 -AppId "<app-id>" -InstallationId <installation-id> -PrivateKeyPath "<path-to-private-key.pem>"When configured, agent-runner injects a fresh GitHub App installation token into idle tasks and issue runs as GH_TOKEN / GITHUB_TOKEN,
so gh pr create (and other gh GitHub operations) run as the app installation and PRs are created by <app-name>[bot].
Fallback option: Bot token (PAT).
- Set
AGENT_GITHUB_NOTIFY_TOKENto a token that can comment on your repos, or save it tostate/github-notify-token.txt. - agent-runner will use that token to post completion/failure comments.
Helper script (writes state/github-notify-token.txt):
.\scripts\set-notify-token.ps1 -Token "<token>"For idle runs that create a PR, agent-runner will try to locate the PR URL from the idle final response or the idle log tail.
If no PR URL is available, it will fall back to searching for an open PR by the idle run's head branch.
When a PR is detected, agent-runner assigns the authenticated user associated with the runner GitHub token to the PR (to trigger GitHub notifications)
and posts an idle completion comment. If a notify client is configured, the comment is posted as the notify identity; otherwise it is posted
using the runner token.
If idle.repoScope is set to "local", idle runs only target repositories under the workspace root.
If idle.usageGate.enabled is true, idle runs only execute when the weekly reset
window is near and unused weekly capacity remains. The weekly threshold ramps
down as the reset approaches. The 5h window is used only to confirm that some
short-term capacity remains (5h reset timing is ignored). The runner reads
rate limits from local Codex session JSONL when available, and falls back to a
backend usage API lookup using auth.json in the Codex home directory.
If idle.copilotUsageGate.enabled is true, idle runs also require Copilot monthly
usage to be within the configured reset window and above the remaining-percent
threshold for that window.
If idle.geminiUsageGate.enabled is true, idle runs also require Gemini usage
to be within the configured reset window and above the remaining-percent threshold.
Note: some Gemini quotas report a ~24h reset time while usage is still 100%, and the countdown
does not start decreasing until the first request is made. When warmup is enabled (default),
agent-runner will schedule a single Gemini idle run (per model, throttled by cooldown) to start
the countdown, and then the normal reset-window gating applies.
If idle.amazonQUsageGate.enabled is true, idle runs also require Amazon Q monthly
usage (tracked by agent-runner for its own runs) to be within the configured reset
window and above the remaining-percent threshold.
When both Codex and Copilot usage gates allow, the runner schedules idle tasks
for both engines (using different repos when available) and will temporarily
raise idle.maxRunsPerCycle if needed to cover both engines.
Example config snippet:
{
"idle": {
"enabled": true,
"maxRunsPerCycle": 1,
"cooldownMinutes": 240,
"tasks": [
"Bring the repo into compliance with AGENTS.md and project docs/standards. Identify the highest-impact gap, fix it, and update docs/tests as needed. If nothing meaningful is needed, exit."
],
"repoScope": "local",
"promptTemplate": "You are running an autonomous idle task. Target repo: {{repo}}. Task: {{task}}. Open PR count: {{openPrCount}}. Open PR context: {{openPrContext}}",
"usageGate": {
"enabled": true,
"command": "codex",
"args": [],
"timeoutSeconds": 20,
"minRemainingPercent": {
"fiveHour": 50
},
"weeklySchedule": {
"startMinutes": 1440,
"minRemainingPercentAtStart": 100,
"minRemainingPercentAtEnd": 0
}
},
"copilotUsageGate": {
"enabled": true,
"timeoutSeconds": 20,
"apiBaseUrl": "https://api.github.com",
"apiVersion": "2025-05-01",
"monthlySchedule": {
"startMinutes": 10080,
"minRemainingPercentAtStart": 100,
"minRemainingPercentAtEnd": 0
}
}
},
"codex": {
"command": "codex",
"args": ["exec", "--dangerously-bypass-approvals-and-sandbox", "--model", "gpt-5.2"],
"promptTemplate": "..."
},
"copilot": {
"command": "copilot",
"args": ["--allow-all", "--model", "gemini-3-pro-preview", "-p"]
}
}Recommended to avoid repo-wide polling and GitHub rate limits.
- Create a GitHub App and set its webhook URL to your Cloudflare Tunnel URL,
for example
https://<tunnel-host>/webhooks/github. - Configure the webhook secret and store it in an environment variable
(example:
AGENT_GITHUB_WEBHOOK_SECRET). - Subscribe to events: Issue comment, Pull request review, and Pull request review comment.
- Install the App on the repositories you want the runner to watch.
- Enable
webhooksinagent-runner.config.jsonand start the webhook listener.
Minimal Cloudflare Tunnel config example (save to a file and run with cloudflared tunnel run):
tunnel: <tunnel-id>
credentials-file: <path-to-credentials.json>
ingress:
- hostname: <tunnel-host>
service: http://127.0.0.1:4312
- service: http_status:404Ensure the webhook listener is running locally on the same host/port as the tunnel target.
Ensure required agent labels exist in all repositories:
node dist/cli.js labels sync --yesqueued: waiting for an available worker slotreviewFollowup: managed PR review follow-up is queuedreviewFollowup:waiting: managed PR follow-up is queued but blocked by idle engine gatesreviewFollowup:action-required: managed PR follow-up stopped and needs manual interventionrunning: currently executing in Codexfailed: execution failed (error path)needsUserReply: execution paused because the agent explicitly needs user input
Quota handling:
- If Codex usage limit is hit, the runner marks the issue as
failed, posts the next retry time, and automatically re-queues at or after that time. - Retry scheduling is persisted on disk, so it still resumes correctly after host restart/offline periods.
User-reply handling:
- When a run pauses for user input, the runner applies
needsUserReply. - After the user replies, the runner re-queues and resumes from the previous Codex session when available.
The runner includes recent user replies in the next prompt (limited and truncated) so the issue body usually does not need edits.
If a request is labeled agent:running but the tracked process exits, the runner marks it as failed and asks to re-run with /agent run.
Visible labels: queued, reviewFollowup, reviewFollowup:waiting, reviewFollowup:action-required, running, done, failed, needsUserReply
stateDiagram-v2
[*] --> queued: /agent run (collaborator)
queued --> running: worker picked
running --> done: success
running --> needsUserReply: agent asks user
running --> failed: execution error
running --> failed: quota reached
needsUserReply --> queued: user replies
failed --> queued: scheduled retry (quota)
failed --> queued: /agent run
done --> queued: /agent run
flowchart TD
W1[When: review event arrives immediately] --> W2{Webhook event}
W2 -->|pull_request_review_comment created| W3[Classify comment body]
W2 -->|pull_request_review submitted| W4[Classify review state]
W3 --> W5{idle enabled and PR is managed}
W4 --> W5
W5 -->|no| X1[No enqueue]
W5 -->|yes| W6[Set requiresEngine and enqueue review queue]
C1[When: each runner cycle starts] --> C2[Managed PR catch-up scan]
C2 --> C3{PR open and not queued running needsUserReply failed}
C3 -->|no| X2[Skip catch-up enqueue]
C3 -->|yes| C4{Review summary}
C4 -->|actionable feedback exists| C5[enqueue requiresEngine true]
C4 -->|approved and no actionable feedback| C6[enqueue requiresEngine false]
W6 --> S1[In the same cycle: schedule from review queue]
C5 --> S1
C6 --> S1
S1 --> S2[take requiresEngine false first]
S2 --> S3{capacity left and engine gate allows}
S3 -->|yes| S4[take requiresEngine true]
S3 -->|no| S5[keep remaining queued]
S4 --> E1[Execute scheduled follow-ups]
S5 --> E1
S2 --> E1
E1 --> E2{requiresEngine}
E2 -->|false| E3[approval path auto-merge check]
E3 --> E4{merge ready}
E4 -->|yes| E5[auto-merge and done]
E4 -->|no| E6[re-enqueue requiresEngine false]
E2 -->|true| E7[run agent follow-up]
E7 --> E8[done or failed or needsUserReply]
When this happens:
- Immediate: webhook review events enqueue follow-ups right away.
- Every cycle: catch-up can enqueue missed follow-ups, then queue scheduling and execution run.
requiresEngineis decided when enqueued, then used during scheduling and execution branching.- On GitHub PR threads, runner now posts state comments for blocked follow-ups:
<!-- agent-runner:review-followup:waiting -->: queued but blocked by idle engine gates (No action required).<!-- agent-runner:review-followup:action-required -->: auto-merge stopped with non-retry reason (Action requiredwith next step guidance).
requiresEngine rules:
true: actionable review follow-up is required (for examplechanges_requested, actionable review comments).false: approval-style handling only (for exampleapproved,no new comments,usage limit,unable to review).
- Most reliable: comment
/agent runon the PR. agent:failedremoval alone is not immediate trigger timing.- Managed PR catch-up intentionally skips
failed/queued/running/needsUserReply.
Register a scheduled task that runs every minute:
.\scripts\register-task.ps1 -RepoPath "." -ConfigPath ".\\agent-runner.config.json"Note:
- The
AgentRunnerscheduled task is intentionally a daemon (scripts/run-task.ps1loops forever). - The task is configured with
MultipleInstances IgnoreNew, so when Task Scheduler tries to start it again (every minute), it may reportLastTaskResult = 0x800710E0("refused the request") even though the already-running instance is healthy. Prefer checking the taskState = Runningand the logs instead of relying onLastTaskResult.
Unregister the task:
.\scripts\unregister-task.ps1Task run logs are written to logs/task-run-YYYYMMDD-HHmmss-fff-PID.log (one file per process start).
The latest task-run log path is also written to logs/latest-task-run.path.
Task meta logs (environment + runner start/end) are appended to logs/task-meta-YYYYMMDD.log and the latest path is written to logs/latest-task-meta.path.
Issue logs (e.g. *-issue-*.log) are appended as output is produced.
Register a webhook listener task (runs at logon):
.\scripts\register-webhook-task.ps1 -RepoPath "." -ConfigPath ".\\agent-runner.config.json"Unregister the webhook task:
.\scripts\unregister-webhook-task.ps1Register a Cloudflare Tunnel task (runs at logon):
.\scripts\register-cloudflared-task.ps1 -RepoPath "."Unregister the tunnel task:
.\scripts\unregister-cloudflared-task.ps1Logs are written to logs/webhook-run-YYYYMMDD-HHmmss-fff-PID.log / logs/webhook-run-YYYYMMDD-HHmmss-fff-PID.err.log and logs/cloudflared-*.out.log / logs/cloudflared-*.err.log.
Webhook meta logs are appended to logs/webhook-meta-YYYYMMDD.log and the latest paths are written to logs/latest-webhook-run.path and logs/latest-webhook-meta.path.
To prune old logs (uses logMaintenance from the config):
agent-runner logs prune --config .\\agent-runner.config.json --yesTo prune old reports (uses reportMaintenance from the config):
agent-runner reports prune --config .\\agent-runner.config.json --yesThe agent runner can use Amazon Q for command line as an idle engine via WSL2.
- Install the CLI inside WSL (Ubuntu example):
mkdir -p /tmp/amazon-q-install
cd /tmp/amazon-q-install
curl --proto '=https' --tlsv1.2 -sSf 'https://desktop-release.q.us-east-1.amazonaws.com/latest/q-x86_64-linux.zip' -o q.zip
python3 -c "import zipfile; zipfile.ZipFile('q.zip').extractall('.')"
chmod +x q/install.sh
./q/install.sh --no-confirm- Login (free Builder ID):
q login --license free --use-device-flowIf q is not on your PATH in non-interactive WSL invocations, run it via a login shell:
wsl.exe -d Ubuntu -- bash -lc 'q login --license free --use-device-flow'Set amazonQ in the config (ships disabled by default):
{
"amazonQ": {
"enabled": true,
"command": "wsl.exe",
"args": [
"-d",
"Ubuntu",
"--",
"bash",
"-lc",
"q chat --no-interactive --trust-all-tools --wrap never \"$1\"",
"--"
],
"promptMode": "arg"
}
}Note: --trust-all-tools is required for fully non-interactive runs, but it allows the agent to execute arbitrary shell commands. Only enable this on a machine you are comfortable with the agent operating on.
At the end of each run, the agent should output a final response in this format:
AGENT_RUNNER_STATUS: done
<message to post to GitHub>
If the task is blocked and the user must reply:
AGENT_RUNNER_STATUS: needs_user_reply
<message asking the user what is needed>
Register a daily label sync task:
.\scripts\register-label-sync-task.ps1 -RepoPath "." -ConfigPath ".\\agent-runner.config.json"Unregister the label sync task:
.\scripts\unregister-label-sync-task.ps1Quick status summary (tasks + recent logs):
.\scripts\status.ps1CLI snapshot (text):
node dist/cli.js status --config agent-runner.config.jsonCLI snapshot (JSON):
node dist/cli.js status --config agent-runner.config.json --jsonPause/resume runner (graceful stop after current work):
node dist/cli.js stop --config agent-runner.config.json
node dist/cli.js resume --config agent-runner.config.jsonStart the local status dashboard in the background:
node dist/cli.js ui start --config agent-runner.config.json --port 4311Then open:
http://127.0.0.1:4311/
Check background UI state:
node dist/cli.js ui status --config agent-runner.config.jsonStop background UI:
node dist/cli.js ui stop --config agent-runner.config.jsonForeground mode (for manual debugging):
node dist/cli.js ui serve --config agent-runner.config.json --port 4311Paths shown in the status UI are clickable and will open the file in Explorer.
The dashboard also shows a Review Follow-ups panel with currently queued follow-up items.
Each row includes status (queued or waiting) and next action guidance so operators can quickly tell whether to wait or intervene.
Reason tags (REVIEW_COMMENT / REVIEW / APPROVAL) are shown with plain-language meanings directly in the panel.
Run a background tray helper to open the status UI and pause/resume the runner:
.\scripts\tray.ps1 -RepoPath "." -ConfigPath ".\\agent-runner.config.json"For startup/background launch without a visible PowerShell window, use:
wscript.exe //B //NoLogo .\scripts\run-tray.vbsThe tray helper also includes log shortcuts (Grafana) and can start/stop the local logging stack.
Tip:
- Double-click the tray icon to open the Status UI.
- Right-click the tray icon to open "Open Logs (Grafana)".
If webhooks.enabled is true, the tray helper will also ensure the webhook listener
is running in the background.
Not applicable. This repository is intended to run locally.