Skip to main content

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-logs and surfaced per task in the Web UI task detail view.
  • Per-LLM-call records: propr log list (--failed for failures only), or the LLM Log page; see Metrics.
  • Set LOG_LEVEL=debug in .env for 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 amd64 is the native, recommended production path. Apple Silicon macOS has also been exercised successfully with Docker Desktop running the published Linux amd64 images under emulation; native arm64 images are not yet available. On Linux, confirm Docker is running and the current user can read/write /var/run/docker.sock without an interactive sudo prompt. On Apple Silicon, start Docker Desktop in Linux-container mode and ensure its default Docker socket is available. In either case, docker info must succeed for the current user before retrying propr check.
  • The CLI on PATH is not the one you installed. Run which propr && propr --version. If npm install -g failed with EACCES, your global npm prefix is root-owned — prefix the command with sudo, and use the same convention every time you install or update (npm prefix -g shows which prefix is in effect). Mixing sudo and non-sudo installs can update a different copy of the CLI.
  • Missing or placeholder .env configuration. propr check reports the detected GitHub auth mode (own App, relay, or demo) and flags missing or placeholder values. Re-running propr setup is safe at any time — it skips satisfied steps, never overwrites .env wholesale, and never deletes data.
  • Mixed private-key variables (own-App mode). HOST_GH_PRIVATE_KEY is a host path for the CLI start path; GH_PRIVATE_KEY_PATH is 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. All PROPR_* and HOST_* paths must be absolute; the launcher and .env parsing 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 stack auto-detects ~/.claude, ~/.codex, ~/.gemini, ~/.config/opencode, and ~/.vibe. Authenticate with the provider CLI first (for example claude 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 --verify smoke-tests each agent image and pinpoints the broken one.
  • Tunnel enabled without a token. propr check fails when PROPR_UI_TUNNEL_ENABLED is set without PROPR_UI_TUNNEL_TOKEN. Run propr tunnel setup with 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.json from 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 both WEB_PUSH_VAPID_PUBLIC_KEY and WEB_PUSH_VAPID_PRIVATE_KEY from one pair or remove both for automatic mode; the subject is independently optional. propr check validates explicit settings without printing keys. WEB_PUSH_ENABLED=false deliberately disables setup/enrollment/delivery. See PWA, Web Push, and Badges for backup/restore and rotation implications.
  • direct_webhook mode without a secret. The API refuses to start in GITHUB_EVENT_INTAKE_MODE=direct_webhook without GH_WEBHOOK_SECRET (the secret is unused in the other intake modes).
  • propr setup exits 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, then propr 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 (or propr remote-status) reports daemon heartbeat, active worker count, Redis connectivity, GitHub App configuration, and per-agent health checks. Then read docker logs propr-daemon and docker 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_WHITELIST and GITHUB_USER_BLACKLIST control 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_webhook mode). GitHub must deliver to POST /webhook on the API service (port 4000) through your reverse proxy, with the same GH_WEBHOOK_SECRET configured 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 AI or propr), or the comment must contain a trigger keyword from PR_FOLLOWUP_TRIGGER_KEYWORDS (for example !propr). Slash commands like /review and /fix are 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_BLACKLIST control 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 inspect shows 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.
  • /review then /fix, or /ultrafix for an automated review-fix loop (remove the ultrafix PR 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 via SYSTEM_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_REPLACEMENTS replacements (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) and replacement.exhausted entries explain every decision. The header's attempt chip links each attempt of the lineage.
  • propr task get <task-id> --json shows attemptNumber, replacesTaskId and replacedByTaskId.
  • 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:

ClassMeaningWhat to do
push_protectionGitHub 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_protectionA 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_forwardSomeone 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.
auth401/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.
networkConnection reset, DNS or TLS failure before GitHub accepted the push.Usually transient — recover and push again.
unknownProPR 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_HEAD
    git push origin <branch>
    git push origin --delete refs/propr/rescue/<taskId>--<timestamp> # once recovered
  • Bundle (on the ProPR host, PUSH_RESCUE_BUNDLE_DIR, by default data/rescue/<owner>/<repo>/<taskId>.bundle). Copy it to a checkout of the repository, then:

    git fetch /path/to/<taskId>.bundle HEAD
    git checkout -B <branch> FETCH_HEAD
    git push origin <branch>
  • Retained worktree (under GIT_WORKTREES_BASE_PATH, marked with .retention-info.json): push its detached HEAD from the host, for example git -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/status returns an OK or auth-expected response — the API is reachable through the tunnel. This is also how propr status probes 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 /health path.
  • 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 on fails clearly without one. Run the propr tunnel setup --token ... --url ... --start command shown in ProPR Connect.
  • The core stack is not running. propr tunnel on refuses to start the sidecar when the stack is down — cloudflared would point at an unavailable api:4000 and look superficially healthy. Run propr start first (or pass --force deliberately).
  • Tunnel enabled on an already-running stack. propr tunnel on starts only the sidecar; the API and workers keep the API_PUBLIC_URL / FRONTEND_URL they started with, so OAuth redirects, cookie security, and attachment links still point at localhost values. Run propr start --restart, or use propr 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/compatibility endpoint 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:4000 and 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.