Troubleshooting
This page is organized by symptom. Find what you are seeing, check the likely causes, run the listed commands, and apply the fix. For routine care — updates, backups, log locations, tuning — see Maintenance.
Where To Look First
Before digging into a specific symptom, these give you the fastest read on the system:
propr check # verifies Docker, images, agents, and GitHub auth mode (--verify smoke-tests agents)
propr status # local stack status, including tunnel reachability
propr remote-status # backend health: daemon, workers, Redis, GitHub auth
propr queue # queue statistics
The /api/status endpoint (shown in the Web UI header) reports daemon heartbeat, active worker count, Redis connectivity, GitHub App configuration, and per-agent health checks.
Logs, in order of usefulness:
docker ps
docker logs -f propr-daemon # launcher installs prefix containers with propr-
docker logs -f propr-worker
docker logs -f propr-api
- The host
logs/directory (PROPR_LOGS_DIR) is mounted into the service containers at/usr/src/app/logs. - Agent session logs are written under
/tmp/claude-logsand surfaced per task in the Web UI task detail view. - Per-LLM-call records:
propr log list(--failedfor failures only), or the LLM Log page; see Metrics. - Set
LOG_LEVEL=debugin.envfor more verbose service logs.
propr check Fails Or The Stack Will Not Start
Symptom: propr check (or bare propr, which runs the same environment checks) exits nonzero, or propr start / the launcher fails before the stack is up.
Likely causes, what to check, and fixes:
- Docker is unavailable or the host is unsupported. Linux
amd64is the native, recommended production path. Apple Silicon macOS has also been exercised successfully with Docker Desktop running the published Linuxamd64images under emulation; nativearm64images are not yet available. On Linux, confirm Docker is running and the current user can read/write/var/run/docker.sockwithout an interactivesudoprompt. On Apple Silicon, start Docker Desktop in Linux-container mode and ensure its default Docker socket is available. In either case,docker infomust succeed for the current user before retryingpropr check. - The CLI on PATH is not the one you installed. Run
which propr && propr --version. Ifnpm install -gfailed withEACCES, your global npm prefix is root-owned — prefix the command withsudo, and use the same convention every time you install or update (npm prefix -gshows which prefix is in effect). Mixingsudoand non-sudoinstalls can update a different copy of the CLI. - Missing or placeholder
.envconfiguration.propr checkreports the detected GitHub auth mode (own App, relay, or demo) and flags missing or placeholder values. Re-runningpropr setupis safe at any time — it skips satisfied steps, never overwrites.envwholesale, and never deletes data. - Mixed private-key variables (own-App mode).
HOST_GH_PRIVATE_KEYis a host path for the CLI start path;GH_PRIVATE_KEY_PATHis the in-container path for the launcher. Set exactly one, matching how you start the stack — mixing them is a common migration mistake. - Relative or
~paths in launcher variables. AllPROPR_*andHOST_*paths must be absolute; the launcher and.envparsing do not expand~or$HOME. - Agent credentials not detected or unreadable. For Claude, Codex, Antigravity, or OpenCode, add the agent with Log in to a new account to let ProPR create and map an isolated directory automatically; no provider
HOST_*path is needed. To reuse host credentials,propr init stackauto-detects~/.claude,~/.codex,~/.gemini,~/.config/opencode, and~/.vibe. Authenticate with the provider CLI first (for exampleclaude auth login,agy login) and create existing-account mount directories before starting — Docker can otherwise create them as root-owned. Vibe additionally needs its prompt cache directory to exist:mkdir -p "/tmp/propr-vibe-prompts-$(id -u)".propr check --verifysmoke-tests each agent image and pinpoints the broken one. - Tunnel enabled without a token.
propr checkfails whenPROPR_UI_TUNNEL_ENABLEDis set withoutPROPR_UI_TUNNEL_TOKEN. Runpropr tunnel setupwith the values from ProPR Connect, or remove the flag. - Browser notifications unavailable. Automatic key setup runs at API startup. Check server output for storage errors, verify the existing instance data mount is writable, and restore corrupt
web-push/vapid.jsonfrom backup (directory 700, file 600, service-account ownership). Repair and restart to retry; do not delete the identity as a routine fix. For explicit keys, set bothWEB_PUSH_VAPID_PUBLIC_KEYandWEB_PUSH_VAPID_PRIVATE_KEYfrom one pair or remove both for automatic mode; the subject is independently optional.propr checkvalidates explicit settings without printing keys.WEB_PUSH_ENABLED=falsedeliberately disables setup/enrollment/delivery. See PWA, Web Push, and Badges for backup/restore and rotation implications. direct_webhookmode without a secret. The API refuses to start inGITHUB_EVENT_INTAKE_MODE=direct_webhookwithoutGH_WEBHOOK_SECRET(the secret is unused in the other intake modes).propr setupexits immediately in CI or a pipe. When stdin is not a terminal, setup cannot prompt. Scaffold non-interactively instead:propr init stack, edit<root>/.env, thenpropr start --no-tui.
Dashboard Shows Unauthorized
Symptom: the Web UI login fails or every request comes back unauthorized.
For the default Connect login, confirm PROPR_WEB_AUTH_MODE=connect, the relay credential, GH_OAUTH_CALLBACK_URL, SESSION_SECRET, and FRONTEND_URL; do not add local OAuth client placeholders. A local callback must be the exact loopback API URL (normally http://localhost:4000/api/auth/github/callback); a hosted callback must match the active tunnel. Sessions are stored in Redis, so a Redis outage also invalidates logins — confirm the Redis container is up with docker ps and check Redis connectivity on /api/status. A custom deployment using PROPR_WEB_AUTH_MODE=github must instead verify its own OAuth client id, secret, and registered callback.
If you use the hosted UI tunnel, the callback lives on the proxy host (https://t-<id>.propr.dev/api/auth/github/callback) and Connect must list that tunnel as active; also note that enabling the tunnel on an already-running stack leaves the API with its pre-tunnel localhost URLs until you run propr start --restart (see tunnel problems below).
After correcting .env, restart the stack (propr start --restart) so the API picks up the new values.
Worker Not Processing Issues
Symptom: you add a processing label to an issue and no task ever appears.
Likely causes and what to check:
- A stack component is down.
/api/status(orpropr remote-status) reports daemon heartbeat, active worker count, Redis connectivity, GitHub App configuration, and per-agent health checks. Then readdocker logs propr-daemonanddocker logs propr-worker. - Repository or agent configuration. Confirm the repository is monitored and enabled (
propr repo list,propr repo toggle owner/repo --enable), the label you applied matches the configured processing labels (PRIMARY_PROCESSING_LABELS), and at least one agent is enabled with valid credentials. - The user is not allowed.
GITHUB_USER_WHITELISTandGITHUB_USER_BLACKLISTcontrol who can trigger work; bot comments are ignored. - Intake latency or exhausted API budget (polling mode). With
GITHUB_EVENT_INTAKE_MODE=polling, detection waits up to one polling interval (POLLING_INTERVAL_MS, default 60 s). A large install can exhaust the GitHub App installation's hourly API budget, after which GitHub rejects requests until the window resets — ProPR retries with exponential backoff and logs a warning suggesting a longer interval. See Polling And GitHub API Rate Limits. - Webhook not reaching the API (
direct_webhookmode). GitHub must deliver toPOST /webhookon the API service (port 4000) through your reverse proxy, with the sameGH_WEBHOOK_SECRETconfigured on both sides. Webhook delivery has no periodic backstop, so a missed event relies on GitHub's redelivery.
A PR Comment Does Not Trigger Follow-Up Work
Symptom: you comment on a pull request and nothing happens.
Likely causes:
- Natural comments need a trigger. The PR must carry a processing label (for example
AIorpropr), or the comment must contain a trigger keyword fromPR_FOLLOWUP_TRIGGER_KEYWORDS(for example!propr). Slash commands like/reviewand/fixare different: from an allowed author they run directly, with no processing label required. - The author is not allowed. Bot comments are ignored, and
GITHUB_USER_WHITELIST/GITHUB_USER_BLACKLISTcontrol who can trigger work. - A job for the same PR is already running. Comments posted meanwhile are batched and handled together once the active job finishes — wait for it rather than reposting.
See PR Automation And Fine-Tuning for the full pickup rules.
An Agent Run Failed
Symptom: a task shows failed in the Web UI, or the issue carries a <trigger>-failed-* state label (for example AI-failed-post-processing — the suffix names the phase that failed).
Where to look:
- The task record. Open the task detail view first: it shows the failure message, current state, selected agent and model, the exact prompt sent to the agent (View Prompt), execution log files (View Logs), the live execution event log, and per-file diffs.
propr task inspectshows all queued and executing work;propr task inspect <task-id>shows one task's current details and run history. - Agent session logs under
/tmp/claude-logs, surfaced per task in the task detail view. - The LLM Log page (or
propr log list --failed) for per-call status when the failure is a model call.
Then check credentials, branch settings, and agent configuration — the usual culprits for repeated failures. Transient git and GitHub failures retry automatically with exponential backoff, so a one-off network error often needs no action at all.
Recovery runs through the PR conversation:
- Add a clearer follow-up comment with stronger instructions.
/switch <model-id>to change the PR's model going forward, or/use <model-id>for a one-off task with a different model./reviewthen/fix, or/ultrafixfor an automated review-fix loop (remove theultrafixPR label to stop it).- Re-run with a smaller scope — see Work Splitting.
- Undo a bad commit with
propr task revert owner/repo <pr> <sha> [comment-id], which runs a signed system task (authorized viaSYSTEM_TASK_SECRET) that resets the branch and force-pushes.
A Task Was Re-Run Automatically, Or Was Not
Symptom: a failed task shows "Replacement attempt N started" in its timeline and a new task for the same issue appeared, or a task that was lost with its worker stayed failed.
ProPR replaces an issue task automatically in two cases (see Reconciliation and automatic replacement runs):
- The run was lost with its worker — no queue job and no running task container remained after a worker restart, host reboot or killed container. One replacement is dispatched; a second loss in the same lineage is final.
- The run ended with a transient provider error (5xx, overloaded, connection reset, timeout) — up to
MAX_PROVIDER_REPLACEMENTSreplacements (default 2). Usage limits (429) are not replaced; they re-queue the same task until the limit resets. GitHub and git failures (for example a GitHub 503 while publishing) are never replaced, because the agent run itself succeeded.
Where to look:
- The task timeline.
replacement.dispatched,replacement.skipped(with the reason) andreplacement.exhaustedentries explain every decision. The header's attempt chip links each attempt of the lineage. propr task get <task-id> --jsonshowsattemptNumber,replacesTaskIdandreplacedByTaskId.- The issue. The final failure comment lists every attempt with a link to each task.
Common reasons a task was not replaced: it was cancelled by a user or withdrawn (cancelled_*), stopped by the stall watchdog, run timeout or cost cap, the issue was closed, it is a goal task (goals use their own recovery) or a PR follow-up task, the cap was reached, or earlier attempts used the whole per-run cost cap.
To turn replacement off, set INFRA_LOST_REPLACEMENT=false (lost runs) and MAX_PROVIDER_REPLACEMENTS=0 (provider errors; the Settings → Workflow & PR Rules → Processing → Provider failure replacements setting, propr setting update max_provider_replacements 0, or MCP update_execution_settings overrides the environment). Restart the worker after changing environment variables.
A Push Was Rejected
Symptom: a task failed with Push of branch … was rejected (<class>), and its timeline, propr task get <task-id> and the GitHub failure comment show a Push rejected section.
ProPR classifies the rejection from git's output and keeps the agent's commits through the salvage ladder. Act on the class:
| Class | Meaning | What to do |
|---|---|---|
push_protection | GitHub secret scanning push protection found a secret in the commits. | Open the unblock URL shown verbatim in the comment to allow the secret if it is a false positive, or remove it from the commits. Then push the recovered commits. |
ruleset_or_branch_protection | A repository ruleset or branch protection rule (GH013/GH006) refused the update. | Review the rule violations in the remote output; adjust the rule or push the work to another branch and open a PR from it. |
non_fast_forward | Someone pushed to the branch while the task ran. | Recover the commits, merge or rebase onto the new branch head, and push. Or post a follow-up comment so ProPR redoes the change on the current head. |
auth | 401/403, an expired installation token, or the GitHub App lacks permission (for example workflows) for this ref. | Check the App installation's repository access and permissions, then recover and push. |
network | Connection reset, DNS or TLS failure before GitHub accepted the push. | Usually transient — recover and push again. |
unknown | ProPR could not classify the output. | Read the remote output in the comment's details block or the worker logs. |
Recover the commits from the location the failure names:
-
Rescue ref (
refs/propr/rescue/<taskId>--<timestamp>on the same repository):git fetch origin refs/propr/rescue/<taskId>--<timestamp>git checkout -B <branch> FETCH_HEADgit push origin <branch>git push origin --delete refs/propr/rescue/<taskId>--<timestamp> # once recovered -
Bundle (on the ProPR host,
PUSH_RESCUE_BUNDLE_DIR, by defaultdata/rescue/<owner>/<repo>/<taskId>.bundle). Copy it to a checkout of the repository, then:git fetch /path/to/<taskId>.bundle HEADgit checkout -B <branch> FETCH_HEADgit push origin <branch> -
Retained worktree (under
GIT_WORKTREES_BASE_PATH, marked with.retention-info.json): push its detached HEAD from the host, for examplegit -C <worktree> push origin HEAD:refs/heads/<branch>, or create a bundle from it (git -C <worktree> bundle create /tmp/rescue.bundle HEAD) before its retention ends.
If the failure says the commits could not be preserved, every rung failed (for example the data directory was read-only); the worker logs list each attempt.
Jobs Stuck In The Queue
Symptom: jobs sit in processing or failed and queue counts stop moving.
What to check:
propr queue # queue statistics
docker logs -f propr-worker # is anything being picked up?
Queue counts come from BullMQ in Redis (/api/queue/stats), so also confirm Redis connectivity on /api/status. To inspect Redis directly, use scan-based queries — avoid KEYS '*' on production:
docker exec propr-redis redis-cli --scan --pattern 'bull:*'
Fix: reset the queue from a source checkout:
npm run daemon:reset # against a production build
npm run daemon:reset:dev # development (tsx, debug logging)
The reset clears all Redis queue data (waiting, active, completed, and failed jobs) and removes processing/done state labels from GitHub issues, so issues can be reprocessed from a clean state. npm run worker:reset and npm run worker:reset:dev perform the equivalent reset before starting a worker. See Resetting Queue State.
Metrics Not Updating
Symptom: dashboard statistics or queue counts stop changing while work is clearly happening.
Check that Redis is running, workers are processing jobs, task records are being written to SQLite, and the API container logs are clean. Dashboard statistics come from the SQLite task tables (/api/stats/*) and queue counts come from BullMQ in Redis (/api/queue/stats).
Hosted UI Tunnel Not Working
Symptom: the hosted UI at app.propr.dev cannot reach your stack, or propr tunnel verify fails.
Start with the verifier and read its checks:
propr tunnel verify
It runs four checks against the public proxy URL and exits nonzero if any fail:
- The cloudflared sidecar container is running — if not, the tunnel was never started or was stopped with
propr tunnel off. GET <url>/api/statusreturns an OK or auth-expected response — the API is reachable through the tunnel. This is also howpropr statusprobes tunnel reachability.GET <url>/returns 404 — this is expected: propr-routing forwards only/api/*and/socket.io/*, so the root URL is intentionally not routed. A 404 at the root is not an error, and neither is a 404 on the legacy/healthpath.GET <url>/socket.io/is reachable — live updates are not blocked at Cloudflare ingress.
Likely causes and fixes:
- No token configured. Starting the tunnel always requires
PROPR_UI_TUNNEL_TOKEN;propr tunnel onfails clearly without one. Run thepropr tunnel setup --token ... --url ... --startcommand shown in ProPR Connect. - The core stack is not running.
propr tunnel onrefuses to start the sidecar when the stack is down — cloudflared would point at an unavailableapi:4000and look superficially healthy. Runpropr startfirst (or pass--forcedeliberately). - Tunnel enabled on an already-running stack.
propr tunnel onstarts only the sidecar; the API and workers keep theAPI_PUBLIC_URL/FRONTEND_URLthey started with, so OAuth redirects, cookie security, and attachment links still point at localhost values. Runpropr start --restart, or usepropr tunnel setup --start, which recreates the stack with the hosted URLs applied. - The hosted UI stops at a version-mismatch screen. The hosted UI calls the public
/api/compatibilityendpoint before login; a definitive contract mismatch blocks with a clear screen instead of running against incompatible endpoints. Upgrade the stack — see Updating. - Something else uses host port 4000. Irrelevant to the tunnel: Cloudflare forwards to the Docker-internal
http://api:4000and bypasses the published host port entirely, so the two cannot conflict.
For the full variable reference and enablement semantics, see ProPR CLI → Hosted UI Tunnel and Production Deployment → Hosted UI Tunnel.