Viewport daemon, relay, and hosted runtime are available in alpha. Surfaces may change.
VIEWPORT
Troubleshooting

Runner won't claim work

A workflow is queued, but the self-hosted worker does not pick it up.

Start with the worker-specific doctor:

vpd worker doctor --json

This checks the paired server, worker identity, workspace root, transport, and detected capabilities without claiming work. For self-hosted runners, outbound polling is the recommended default:

vpd worker start --mode persistent --transport polling

If the app gave you a managed-executor registration profile or explicit runner command, diagnose that exact profile instead:

vpd worker doctor --registration-profile ~/.viewport/managed-executors/acme-runner.json --json

or:

vpd worker doctor --json \
  --server https://api.getviewport.com \
  --workspace WORKSPACE_ID \
  --executor EXECUTOR_ID \
  --credential-file ~/.viewport/profiles/local/runner/acme-runner-token \
  --runner-pool default \
  --workdir "$HOME/.viewport/worktrees"

The managed-executor doctor reports the credential source but never prints the token.

The runner is paired but offline

Symptoms:

  • the app shows the runner as offline or stale;
  • workflow runs stay queued for runner claim;
  • vpd worker doctor cannot find an active worker profile.

Fix:

vpd pair PAIRING_CODE --worker --transport=polling --workdir "$HOME/.viewport/worktrees"
vpd worker doctor
vpd worker start --mode persistent --transport polling

If you use a self-hosted compatible control plane, pair with the explicit server:

vpd pair PAIRING_CODE --worker --server https://viewport.customer.internal --transport=polling --workdir "$HOME/.viewport/worktrees"

--workdir belongs on the pair command because pairing writes the worker profile. vpd worker start reads that stored profile. If you use multiple local profiles, check vpd worker doctor --json and confirm vpdProfile.home points at the profile you meant to run before starting the worker.

The worker says another worker is already running

vpd worker start prevents two persistent workers from claiming the same server/workspace/executor at once. If you see:

Workflow worker already running for this server/workspace/executor

find and stop the older process, then start the worker again. If you only want a single proof run, use the one-shot mode instead of a second persistent process:

vpd worker stop
vpd worker start --mode persistent --transport polling

For a proof-only run:

vpd worker run-once --lease LEASE_TOKEN --transport polling

The lock is local and scoped to the paired server/workspace/executor. Stale locks are removed automatically when the recorded process no longer exists, and vpd worker stop clears the stale lock directly.

The runner is online but not claimable

Common causes:

  • the workflow targets a runner pool this worker does not advertise;
  • the required agent or model is not installed locally;
  • the workspace root does not exist or is not writable;
  • another persistent worker is already active for the same server/workspace/executor;
  • workspace policy denies new leases;
  • the runner was revoked after pairing.

Run:

vpd worker doctor --json

If your runner is still using the managed-executor command generated by the app, include the same --registration-profile or explicit --server --workspace --executor --credential-file flags in the doctor command. Otherwise the doctor will check the standalone paired profile instead of the managed-executor profile you are actually running.

Then check the run or route readiness panel in the app. Hosted Viewport should show a structured denial such as runner_pool_unavailable, runner_capability_missing, runner_revoked, or lease_expired. UI hiding is not authority; the server decides whether a worker can claim.

Persistent runner vs. ephemeral lease

Do not debug these as the same thing.

RuntimeExpected behavior
Persistent workerStays online, heartbeats, advertises capabilities, claims many leases. If it exits unexpectedly, it can become stale.
Ephemeral run-once workerClaims one lease, syncs cleanup, and exits. A clean exit should not be treated as a stale persistent runner.

For one-shot execution:

vpd worker run-once --lease LEASE_TOKEN --transport polling

For normal self-hosted runner operation:

vpd worker start --mode persistent --transport polling

A run is blocked at approval

If the run is waiting for PM, engineering, or provider-action approval, the worker should hold or reattach to the active lease rather than starting a duplicate local run. Approve, request changes, or reject from the run detail or inbox. If the worker was restarted, start it with the same paired profile:

vpd worker start --mode persistent --transport polling

The run detail should show the lease id, authority digest, current gate, and cleanup receipt when the run completes.

Policy denies the claim

For hosted Viewport, workspace and workflow policy are server-side admission checks. A self-hosted vpd binary cannot authorize itself to run hosted work.

Expected behavior:

  • new assignments and leases are denied;
  • already-issued leases may be allowed to send final cleanup receipts;
  • credential materialization and side-effect grants are denied for new work.

Policy sync can also deny a route before any worker claim exists. A common launch-path example is a human approval gate whose reviewer tag has no matching team member. Fix the team member tags, then sync the .viewport source again.

If a denial looks wrong, capture:

vpd worker doctor --json > /tmp/vpd-worker-doctor.json

Then copy the run id, route id, runner id, and denial code from the app.

Where to go next

On this page