Appearance
Troubleshooting
Every section below follows the same shape: symptom → what it means → what to check, in order. Failures in the app link straight to the matching section where a code is known. If you fix something mid-run, remember Rerun on the record re-fetches the ticket and starts fresh.
When something isn't covered here, see Sending a good report at the bottom.
PAT scopes
The single most common source of setup failures is a token that exists but can't see what it needs. The exact requirements per provider:
GitHub — classic PAT
- Needs the full
reposcope.public_repois not enough — private repos become invisible and read as 404s. - For Ticketing use, the same
reposcope covers issues.
GitHub — fine-grained PAT (the one that bites people)
- Resource owner must be the account/org that owns the repo.
- Repository access must explicitly include the repo — the default is "no repositories" or a selected list that silently excludes new repos.
- Permissions: Contents: Read and write, Pull requests: Read and write, Metadata: Read (and Issues: Read and write if the same token backs a Ticketing module).
- Editing a fine-grained token's repository access does not change the token value — you don't need to re-enter it in Tenbi. Just fix the access on GitHub and press Verify again.
Bitbucket Cloud — app password / API token
- Permissions: Repositories: Read and write, Pull requests: Read and write. The credential also carries your account email.
Azure DevOps — PAT
- Code: Read and write at minimum. The organization lives on the Version Control module; repos are named
project/repo(notowner/repo).
Jira Cloud — API token
- The credential needs the token, the account email, and the site URL (e.g.
https://yourteam.atlassian.net). The token inherits your user's project access — if you can't see a project in Jira, neither can Tenbi.
Shortcut — API token
- One token is all it needs; it inherits your workspace access.
VCS repo not found
Symptom: the Verify button on a Lane repo row fails with not found / 404, or a run fails while cloning a repo that definitely exists.
What it means: the provider returned 404 for the repo. On GitHub and Bitbucket, a private repo your token can't access returns 404, not 403 — it reads as "doesn't exist" but almost always means the token can't see it, not that the repo is missing or must be public.
Check these, in order:
- Typo / format — the repo name is
owner/repo(GitHub/Bitbucket) orproject/repo(Azure). No URL, no.gitsuffix. - Token scope — walk the PAT scopes list for your provider. For a GitHub fine-grained PAT, the repository allow-list is the usual culprit: the token was created before this repo, or with "selected repositories" that don't include it.
- Right credential — the Lane's Version Control module points at the credential you think it does (its repo row in project Settings names the connection). A lane's repos are all verified with that single module's token — it must reach every repo on the Lane.
- Owner mismatch — for fine-grained PATs, the resource owner must be the repo's owner (personal token can't see org repos unless the org allows it).
After fixing access on the provider side, press Verify again — tokens don't need re-entering unless the token value itself changed.
VCS auth failed
Symptom: Verify or a run fails with an authentication error (401) rather than not-found.
What it means: the provider rejected the token itself — expired, revoked, or pasted incorrectly.
Check these:
- The token still exists and hasn't expired (fine-grained GitHub PATs and Azure PATs have expiry dates; classic PATs can be revoked by org policy).
- Re-paste the credential in Settings → Credentials (a stray space or a truncated paste is common), then Verify.
- If your org enforces SSO, the token must be authorized for the org (GitHub: "Configure SSO" on the token page).
VCS access forbidden
Symptom: a 403 / forbidden error on verify, clone, push, or PR-open.
What it means: the token is valid but the action isn't allowed.
Check these:
- Write permission — pushing branches and opening PRs needs read and write (
reposcope / Contents+Pull requests R/W — see PAT scopes). - Branch protection rules that block the push target.
- Rate limiting — heavy verify/clone activity can trip provider rate limits; they clear on their own after a few minutes.
Workflow file push rejected
Symptom: a run completes every phase but fails at the final step with reason devops_open_pr_failed, and the log shows [remote rejected] … refusing to allow a Personal Access Token to create or update workflow .github/workflows/… without workflow scope.
Note: by default Tenbi's protected-paths gate strips unrequested workflow-file changes before push and offers them as a PR suggestion instead — so you'll normally only hit this rejection on tickets labeled
allow-protected-paths(or on Lanes where the gate is disabled).
Cause: the change modified a GitHub Actions workflow file (anything under .github/workflows/), and your GitHub PAT doesn't carry the scope GitHub requires to push workflow changes. GitHub enforces this regardless of the token's other repository permissions — the branch push is refused, so no PR opens.
Fix:
- Add the workflow scope to the token:
- Classic PAT — check the
workflowscope (alongsiderepo). - Fine-grained PAT — set Workflows: Read and write in the token's repository permissions.
- Classic PAT — check the
- A classic token's value changes when you edit its scopes → re-paste it in Credentials and press Verify. A fine-grained token's value does not change on a permission edit — just Verify again.
- Rerun the ticket. The work re-runs and the push succeeds this time; the failed attempt never pushed a branch, so there's nothing to clean up.
Prefer the agents never touch CI files? Leave the scope off — those tickets will simply fail at push, which is a safe, visible stop rather than a silent CI change.
Model key invalid
Symptom: runs fail almost immediately (seconds of runtime), often showing a generic failure like "Runner container died unexpectedly" or "Anthropic credential needed." A credential can also expire mid-run: the run works normally for minutes, then dies with an auth line in the log — same cause, same fix.
What it means: Anthropic rejected the run's Claude credential. In the record's log you'll typically find a line like [claude] Failed to authenticate or 401 Invalid authentication credentials. Without a valid key the agents can't start at all, so the run dies fast.
Check these:
- Settings → Credentials → Anthropic has a credential for your account — an account with no Claude credential can't run anything.
- OAuth token (subscription) mode: these tokens expire. Generate a fresh one with
claude setup-tokenand re-paste it. - API key (metered) mode: confirm the key is active in the Anthropic Console and has access to the model your Lane uses (a key without access to the selected model fails the same way).
- Re-run the record once the credential is fixed.
Invalid model id
Symptom: a run fails almost immediately, before any Coding activity, with an "invalid model id" failure — no container work happened.
What it means: the model id resolved for this run (Lane override, or the Settings default) isn't one Tenbi recognizes. This is checked before a runner container is even spawned, so no run time or spend is wasted — previously an unrecognized id ran silently on Claude Code's own default model instead (a quiet quality downgrade); it now fails loud instead.
Check these:
- Lane → Model — confirm the override matches one of the options in the picker exactly (copy/paste rather than retype, if unsure).
- Workspace → Settings → Models → Workspace base model — same check, if no lane or project override is set.
- If you believe the id you set IS a real, current Anthropic model that Tenbi's picker doesn't yet offer, it needs to be added to Tenbi's known-model list before it can be used — this isn't something you can self-serve.
Token expired
Symptom: a Lane that worked for weeks starts failing verifies or runs with auth errors, with no config changes.
What it means: an expiring credential (fine-grained GitHub PAT, Azure PAT, Anthropic OAuth token) hit its expiry date.
Check these: which credential is old (Settings → Credentials shows a hint of each stored secret), regenerate it at the provider, paste the new value into the same credential — every module using it picks up the change — and Verify.
No tickets picked up
Symptom: you marked a ticket ready but no record appears on the Dashboard.
What it means: the Lane's discovery didn't match the ticket, or the Lane isn't polling.
Check these, in order:
- The Lane is enabled (Lanes list — the toggle).
- Give it a poll cycle — discovery runs about every 30 seconds; press Refresh on the Dashboard.
- Label trackers (GitHub): the ticket carries all of the Lane's discovery labels, spelled exactly (labels are matched by name).
- Status trackers (Jira / Shortcut): the ticket sits in the Lane's exact Ready status. On Jira, if the Ticketing module has a project key, only that project is searched — a ticket in another project is invisible.
- The ticket hasn't already run — a ticket with an existing record is never re-picked-up by the discovery trigger; re-running takes a distinct signal (see Re-run not triggering).
- Done/archived stories (Shortcut) are skipped by discovery on purpose.
Re-run not triggering
Symptom: a ticket that already ran won't run again, even though it's labeled / in Ready.
What it means: finished tickets are deduplicated by their run record — the discovery trigger alone never re-runs them. Re-running takes its own signal.
Check these:
- Label trackers (GitHub): add the Lane's re-run label (default
ai-redo) — re-applying the discovery label does nothing. - Status trackers (Jira / Shortcut): move the ticket back to Ready. This only works when the Lane's In Progress stage is mapped — that's what moved the ticket out of Ready on pickup, making its return unambiguous. If In Progress is unmapped, use the Rerun button on the record instead.
- Any run can always be re-run from its record page (Rerun re-fetches the ticket, so your edits are picked up).
The Rerun button pre-flights blockers before it spends an attempt, and a re-run can also be triggered automatically when
mainmoves under finished work. For what the Conflicts with main, Needs re-run, and Waiting on #N states mean and what each costs, see When Work Overlaps.
Ticket status not updating
Symptom: runs work fine, but the ticket doesn't move on the board (or labels don't change).
What it means: lifecycle updates are best-effort — a tracker API failure is logged but never fails the run. On Jira there's a specific rule: status moves go through transitions, so the target status must be reachable from the ticket's current status in your Jira workflow, or the move is skipped.
Check these:
- The stage is actually mapped on the Lane (blank stages are skipped by design).
- Jira: the workflow allows the transition (e.g. your workflow may not allow Ready → Done directly). Statuses you map on a Lane have to be connected in the Jira workflow.
- The record's event log — skipped lifecycle updates are noted there.
Run failed — how to triage
Symptom: a record shows Failed.
Where to look, in order:
- The failure callout at the top of the record — it names the reason (e.g. Anthropic credential needed, Monthly quota reached) and links here when the cause is known.
- The live log — scroll to the end; the real error is usually in the last dozen lines. This matters especially for container deaths, where the headline reason is generic but the log has the specific cause.
- Artifacts — if the run got as far as planning/coding, the PRD and design docs show what the agents understood; a wrong turn is often visible there.
- Fix the cause, then Rerun. Reruns re-fetch the ticket, so ticket edits are picked up.
If the failure is a Needs re-run / Conflicts with main state — a conflict after main moved rather than a run-time error — the fix is a click, never a git command: see When Work Overlaps.
Runner container died
Symptom: failure reason "Runner container died unexpectedly."
What it means: the container running the agents exited without reporting a result. The watchdog caught it. This is a symptom, not a cause — the cause is almost always in the log.
Check these:
- Open the record's log and read the last lines. The most common underlying cause is a rejected Claude credential — a
Failed to authenticateline means it's really Model key invalid. - A run that died mid-work with no error lines may have hit host resource limits — re-run it; repeated silent deaths on the same ticket are worth reporting.
Run stuck in one state
Symptom: the duration keeps climbing but the state hasn't changed in a long time.
What it means: usually nothing — Coding is genuinely long on real tickets (often many minutes). A truly stuck run is failed automatically by the watchdog after its grace period.
Check these:
- Open the record — if the live log is still moving, it's working; leave it.
- If the log has been silent well past normal, you can Cancel and rerun.
- Runs sitting in Queued aren't stuck — they're waiting for a free run slot (concurrency caps) or, on Optimize Lanes, for artifact generation.
Run timed out
Symptom: failure reason "Run timed out."
What it means: the run exceeded the maximum allowed wall-clock and was stopped.
Check these: oversized tickets are the usual cause — split the ticket into smaller, well-scoped pieces (the agents do markedly better on focused tickets), then rerun.
Monthly quota reached
Symptom: failure reason "Monthly quota reached"; new runs fail immediately.
What it means: your account hit its configured monthly run cap or token budget.
Check these: the quota resets at the start of the next month; if you need the cap raised sooner, contact support with your account name.
Workspace preparation failed
Symptom: failure reasons like "Could not prepare the repo worktree,""Two repos resolve to the same workspace folder," or "No repos configured on the Lane."
What it means: the run failed before any agent started, while assembling the multi-repo workspace.
Check these:
- No repos configured — the Lane needs at least one repo row.
- Duplicate workspace folder — two repos on the Lane share the same role; roles become folder names, so each repo's role must be unique (
frontend/api, notapp/app). - Worktree preparation failed — usually a clone problem in disguise: run Verify on each repo row and walk VCS repo not found if one fails. A per-repo base branch that doesn't exist also lands here (Verify checks the branch when one is set).
Runner OOM capped
Symptom: failure reason "Runner hit its memory cap and was killed."
What it means: the run's container exceeded its memory cap and the kernel OOM-killed it (detected via the container's OOM-kill flag at teardown). This is almost always the tenant's build/test tooling, not the agents themselves — test runners default to one worker per core, and tsc holds the whole type graph in memory. Rather than let the run die silently, Tenbi surfaces it as this explicit failure.
Check these:
- Raise the Lane's memory cap. Lane → Execution → Runner memory → Memory cap (MB). The run detail shows the run's peak RSS, and the Lane view shows peak-RSS history (min / median / p99); set the cap above the p99 with headroom.
- Bound the spike source instead of (or as well as) raising the cap. On the same section, set Node max old space (MB) and Test max workers — these cap V8's heap and the test runner's worker count inside the container. Fewer workers is often enough to fit a run under the cap.
- If you set no manual cap, Tenbi adapts the cap from this Lane's own history once it has ≥20 recorded peaks (p99 × 1.5). A first heavy run on a new Lane can still OOM before that history exists — raise the cap manually.
Stack unsupported
Symptom: a run ends within seconds of starting — before any phase — with failure reason "Repo stack not supported yet". The detail either names a stack on the roadmap ("Tenbi currently executes JS/TS, Python, Go, .NET, Ruby, Rust and Java; <stack> support is on the roadmap.") or, for a Poetry/PDM Python repo, explains that the packaging tool isn't supported yet.
What it means: the runner image executes JavaScript/TypeScript (npm/Node), Python (Beta), Go (Beta), .NET (Beta), Ruby (Beta), Rust (Beta), and Java (Beta) toolchains. At workspace prep — before any AI phase starts and before any runner spend — Tenbi scans each repo's manifests and, if it finds a stack it can't execute (PHP), fails the run immediately rather than letting it flail mid-run when php turns out to be missing. Multi-stack and multi-repo workspaces are checked as a union: every required toolchain must be executable, or the run fails listing the ones that aren't. This is deterministic — no model spend is incurred.
Check these:
- Confirm the repo's stack. The failure detail names the stack(s) that tripped the check (e.g.
PHP). Tenbi runs JS/TS, Python, Go, .NET, Ruby, Rust and Java today; the other stacks are on the roadmap, not yet executable. - Poetry- or PDM-managed Python? Plain Python (pip /
pyproject.toml/requirements.txt/setup.py) is supported, but a repo with apoetry.lockorpdm.lockfails on purpose — those tools manage their own environments and aren't supported yet. Remove the lock file to use the standard pip path, or wait for Poetry/PDM support. See Supported stacks for the current matrix. - Polyglot JS/Python repo? If the repo is genuinely JS/TS or Python but ships an unrelated foreign build manifest (a stray
Gemfile,pom.xml,*.csproj, …) outside a vendored directory, the union check treats it as a required toolchain. Manifests undernode_modules/,vendor/,.venv/, andvenv/are ignored; move or remove an incidental foreign manifest that isn't part of a real build. - Waiting on a stack? The remaining stacks (and Poetry/PDM) are planned but not shipped — there is no Lane setting that enables them today.
Python pin below floor
Symptom: a Python run ends within seconds of starting — before any phase — with failure reason "A repo pinned a Python version below the supported floor". The detail names the pin and the floor, e.g. ".python-version: 3.7 is below the supported floor (3.9); declare 3.9+ or remove the pin."
What it means: Tenbi runs Python on a set of supported 3.x interpreters (the support window, currently 3.9–3.14). A repo's .python-version file pinned a minor below that window. Rather than silently substitute a different interpreter than you pinned — which previously clamped an old pin up to a build that fails to install and then died as an opaque "container died" hang — Tenbi refuses the pin at resolution, before any container is provisioned (no runner spend). This is deterministic and incurs no model spend.
The floor exists because Tenbi keeps GitHub attestation verification ON for the python-build-standalone assets it installs (supply-chain posture). Interpreter builds older than the floor predate attestations and are uninstallable under that policy. The floor is empirical and re-audited on every toolchain/image bump.
Check these:
- Read the failure detail. It names the exact pin (e.g.
3.7) and the current floor (e.g.3.9). - Declare a supported version. Set
.python-versionto a minor at or above the floor (3.9–3.14). The newest supported interpreter is the default when you leave the pin off. - Or remove the pin. Delete
.python-version(or replace it with arequires-pythonfloor inpyproject.toml, e.g.requires-python = ">=3.9") — arequires-pythonfloor resolves up to the newest supported interpreter and can never land below the window, so it never trips this check. - Above the window? A pin above the window is not this error — it clamps down to the newest supported minor and runs. Only pins below the floor are refused.
Python range unsatisfiable
Symptom: a Python run ends within seconds of starting — before any phase — with failure reason "A repo's requires-python range excludes every supported Python". The detail names the offending range and the window, e.g. "no supported Python satisfies '❤️.9'; supported: 3.9–3.14."
What it means: Tenbi runs Python on a set of supported 3.x interpreters (the support window, currently 3.9–3.14). A repo's pyproject.toml declared a requires-python range whose ceiling sits below that window (e.g. <3.9), or that otherwise overlaps it nowhere (e.g. ==3.8.*, or a contradictory >=3.12,<3.11). Tenbi resolves requires-python to the newest in-window minor that satisfies the whole range — so when the range excludes every supported interpreter, running the newest one anyway would violate the ceiling you declared. Rather than silently run an interpreter your range forbids, Tenbi refuses the range at resolution, before any container is provisioned (no runner spend). This is deterministic and incurs no model spend.
This is the ceiling-side companion to "Python pin below floor": together they make Python resolution honest at both edges — a floor that's too low is refused as a pin, a ceiling that's too low is refused as a range.
Check these:
- Read the failure detail. It names the exact range (e.g.
<3.9) and the supported window (3.9–3.14). - Widen or correct the range. Edit
requires-pythoninpyproject.tomlso it overlaps the window — e.g.">=3.9", or">=3.10,<3.13"(which resolves to 3.12, the newest minor below the ceiling). A range like">=3.10"resolves to the newest supported interpreter (3.14). - Above the window? A pure floor above the window (e.g.
>=3.15) is not this error — it clamps down to the newest supported minor (3.14) and runs. Only a ceiling below the window (or a range with no overlap) is refused. - Pinned as well? A valid
.python-versionpin overridesrequires-python, so a pinned repo is governed by the pin, not the range — fix the pin instead (see Python pin below floor).
Go pin below floor
Symptom: a Go run ends within seconds of starting — before any phase — with failure reason "A repo pinned a Go toolchain below the supported floor". The detail names the pin and the floor, e.g. "go.mod toolchain: go1.18 is below the supported floor (1.21); declare go1.21+ or drop the toolchain directive."
What it means: Tenbi runs Go on a set of supported 1.x toolchains (the support window, currently 1.21–1.26). The repo's go.mod carries a toolchain directive pinning a minor below that window. Rather than silently run a different toolchain than the one pinned, Tenbi refuses the pin at resolution, before any container is provisioned — deterministic, no model spend.
Check these:
- Read the failure detail. It names the exact pin and the current floor.
- Raise or drop the
toolchaindirective. Atoolchain go1.21+ pin runs as declared. Dropping the directive entirely lets thegodirective govern — and that resolves up to the newest supported toolchain (Go's compatibility promise makes a newer toolchain safe for older code), so it never trips this check. - Above the window? A
toolchainpin above the ceiling is not this error — it clamps down to the newest supported toolchain and runs.
Go floor above ceiling
Symptom: a Go run ends within seconds of starting with failure reason "A repo's go directive requires a newer Go than the supported ceiling", e.g. "go.mod: go 1.27 requires a newer Go than the supported ceiling (1.26)".
What it means: the repo's go.mod go directive declares a minimum required Go version above Tenbi's newest supported toolchain (currently 1.26). Running an older toolchain would violate the module's own declaration — the go command itself refuses — so Tenbi fails the run at resolution instead of mid-phase. The ceiling tracks Go's release cadence and is raised on image audits; a just-released Go minor may briefly sit above it.
Check these:
- Lower the
godirective to the supported ceiling or below, if the module doesn't genuinely need newer-Go features. - Or wait for the ceiling raise — Tenbi audits and raises the window after each Go release.
Dotnet pin below floor
Symptom: a .NET run ends within seconds of starting with failure reason "A repo pinned a .NET SDK below the supported floor", naming the pin and the floor (SDK 8).
What it means: global.json pins an SDK major older than Tenbi's support window (8–10). Rather than silently run a different SDK than the one pinned, Tenbi refuses at resolution — before any container, no model spend.
Check these:
- Raise
global.jsonto SDK 8+ (or delete it — without a pin Tenbi runs the newest supported SDK, which builds all supported TargetFrameworks).
Dotnet TFM above ceiling
Symptom: a .NET run stops early with "A project targets a newer .NET than the supported SDK ceiling".
What it means: a TargetFramework names a .NET major above the newest SDK Tenbi serves (currently 10). Down-targeting only goes down — SDK 10 cannot build net11.0 — so the run is refused up front. The ceiling tracks .NET's November release cadence and is raised on image audits.
Check these:
- Lower the TargetFramework to a supported major, or wait for the ceiling raise after the next .NET release.
Dotnet windows only
Symptom: a .NET run stops early with "A project targets Windows-only .NET, which Linux runners cannot execute", listing the offending TargetFrameworks.
What it means: the repo targets .NET Framework (net472, net48, …) or a -windows suffixed TFM (WPF/WinForms class). Tenbi's runners are Linux — these targets can never execute here. This is a platform limit, not a roadmap item: it will not change with a future stack addition.
Check these:
- Cross-platform projects only. If the Windows-only project is incidental (a legacy tool in a mixed solution), move it out of the repo or exclude it; the modern (
netX.0) projects run fine.
Ruby pin below floor
Symptom: a Ruby run ends within seconds of starting with failure reason "A repo pinned a Ruby version below the supported floor", naming the pin and the floor (3.2).
What it means: .ruby-version (or an exact ruby "…" pin in the Gemfile) names a Ruby older than the support window (3.2–3.4; 3.1 and older are end-of-life upstream). Tenbi refuses at resolution — before any container, no model spend — rather than silently running a different Ruby than the one pinned.
Check these:
- Raise the pin to 3.2+ (or remove it — without a pin Tenbi runs the newest supported Ruby).
- Constraint instead of pin? A Gemfile floor like
ruby ">= 3.2"resolves up automatically and never trips this check.
Ruby range unsatisfiable
Symptom: a Ruby run stops early with "A repo's Gemfile ruby requirement excludes every supported Ruby", quoting the constraint.
What it means: the Gemfile's ruby constraint has no overlap with the supported window (e.g. "< 3.2"). Running a Ruby the constraint excludes would violate the repo's own declaration — Bundler itself would abort — so Tenbi refuses up front instead of mid-run.
Check these:
- Widen or update the constraint so it includes a supported version (3.2–3.4), or remove the directive to run the newest supported Ruby.
Rust pin below floor
Symptom: a Rust run ends within seconds of starting with failure reason "A repo pinned a Rust version below the supported floor", naming the pin and the floor (1.85).
What it means: rust-toolchain.toml (or the legacy rust-toolchain file) pins a Rust older than the support window (1.85–1.97). Tenbi refuses at resolution — before any container, no model spend — rather than silently running a different Rust than the one pinned.
Check these:
- Raise the pin to 1.85+ (or remove the file — without a pin Tenbi runs the newest supported Rust).
- Minimum instead of pin? Cargo.toml's
rust-versionis a floor and resolves up automatically; it never trips this check.
Rust MSRV above ceiling
Symptom: a Rust run stops early with "A repo's Cargo.toml rust-version requires a newer Rust than the supported ceiling".
What it means: the crate's declared minimum Rust (rust-version, the MSRV) is newer than the newest Rust Tenbi ships (1.97). Building with an older toolchain would violate the crate's own declaration — cargo itself refuses — so Tenbi fails up front instead of mid-run. The ceiling advances with runner image updates.
Check these:
- Lower
rust-versionif the crate doesn't actually need the newer compiler, or wait for the ceiling to advance.
Rust channel unsupported
Symptom: a Rust run stops early with "A repo pins a nightly or beta Rust channel; Tenbi runs stable Rust only", quoting the channel.
What it means: rust-toolchain.toml names nightly or beta (dated or not). Nightly moves every day — a run today and a re-run next week would build with different compilers, which breaks reproducibility and Tenbi's pinned- toolchain model. This is a deliberate v1 limit, not an oversight.
Check these:
- Pin a stable version (1.85–1.97) or remove the channel pin. Crates that genuinely require nightly features can't run on Tenbi yet.
Java pin below floor
Symptom: a Java run ends within seconds of starting with failure reason "A repo pinned a JDK below the supported floor", naming the pin and the floor (11).
What it means: .java-version, .sdkmanrc, or a Gradle toolchain declaration names a JDK older than the support window (11–25). Tenbi refuses at resolution — before any container, no model spend — rather than silently running a different JDK than the one pinned.
Check these:
- Raise the pin to 11+ (or remove it — without a pin Tenbi runs JDK 25).
- Bytecode target instead of pin?
maven.compiler.releaseand friends are minimums — a newer JDK builds them via--releaseand never trips this check.
Java release above ceiling
Symptom: a Java run stops early with "A repo targets a newer Java than the supported JDK ceiling".
What it means: a build file's bytecode target (maven.compiler.release, java.version, Gradle compatibility) names a Java newer than the newest JDK Tenbi ships (25). Compiling a future release level is impossible — javac itself refuses — so Tenbi fails up front instead of mid-run. The ceiling advances with runner image updates.
Check these:
- Lower the target if the code doesn't actually need the newer release, or wait for the ceiling to advance.
Java android unsupported
Symptom: a run stops early with "An Android project was detected; runners carry no Android SDK", naming the offending build files.
What it means: the repo applies the com.android.application/library Gradle plugin or contains an AndroidManifest.xml. Android builds need the Android SDK, platform images, and license acceptance — none of which exist in Tenbi's runners. This is a platform limit, not a roadmap item.
Check these:
- Mixed repos: if the Android module is incidental to a JVM service in the same repo, split it out — the plain-JVM modules run fine.
Rate limited
Symptom: a run fails and the log tail mentions rate limit, too many requests, or overloaded.
What it means: a provider throttled the run mid-flight — the Anthropic API (429/overloaded), or your VCS/ticketing provider's API.
Check these: wait and rerun — limits are time-windowed and clear on their own. If it recurs, you're likely running too many concurrent runs against one account: lower the Lane's concurrency, or spread repos across credentials. An Anthropic 429 on a subscription credential can also mean the subscription's own usage window is exhausted.
Disk full
Symptom: a run fails and the log tail mentions ENOSPC or "no space left on device."
What it means: the orchestrator host ran out of disk while the run was writing (worktrees, dependency installs, build output).
Check these: this is an operator-side condition — report it. Operators: check the data volume (worktrees + repo caches), docker image/log accumulation, and whether the workspace GC retention window is too long for the disk.
Dependency install failed
Symptom: a run fails early in Coding and the log tail shows npm/pip install errors (npm ERR!, ERESOLVE, "No matching distribution").
What it means: the agent couldn't install the repo's dependencies inside the runner, so it never got a working build to code against.
Check these:
- Lockfile drift — does a clean install pass locally (
npm ci/pip install -r requirements.txt) on the Lane's base branch? - Private registries — the runner has no npm/pip auth beyond the repo itself; a dependency from a private registry will fail. Vendor it or make it public to the runner.
- Unusual registry hosts — runner egress allows the public internet, but a registry behind a VPN/LAN is unreachable by design.
Optimization run failed
Symptom: an Optimization record shows "Optimization failed" on a Lane with Optimize enabled.
What it means: the generation run (which surveys the repo and drafts the map + guide) couldn't finish. Ticket runs on the Lane still work — they just run without optimization until generation succeeds.
Check these:
- The repo verifies cleanly (generation clones it like any run).
- Your Claude credential is valid — generation runs on the same key (Model key invalid).
- Retry via Generate on the Lane. Very unusual repo layouts can defeat the indexer; if it fails repeatedly on one repo, report it.
Sending a good report
When you're stuck, include these five things and support can usually answer in one round-trip:
- The record id (URL of the record page) — or the Lane name if nothing was created.
- What you expected vs what happened.
- The failure callout text (reason + detail), if any.
- The last ~20 lines of the record's log.
- What you already tried from this page.
Subscription (OAuth) authentication
Tenbi supports an advanced credential mode that uses a Claude subscription (OAuth) token instead of an API key. This mode is not supported by Anthropics terms. Anthropics Consumer Terms prohibit using Claude subscription credentials in third-party services (sections 2 and 3.7, clarified February 2026), and Anthropic enforces this with automated account suspensions. Using a subscription token with Tenbi can get your Anthropic account banned — mid-run, without warning — and Tenbi cannot prevent or reverse this.
The supported path is an Anthropic API key (Settings → Credentials). API usage is billed by Anthropic to you at the same per-token prices you would pay them directly; Tenbi never marks up or meters your usage.
If you still choose the OAuth mode, Tenbi requires a one-time acknowledgment of this risk before the token can be saved. If a run fails with an authentication error while in OAuth mode, check whether the token has expired or the account has been restricted, and consider switching to an API key.
Codex authentication failed
A Codex run that dies at startup with codex_auth_setup_failed (or a boot failure naming it) could not authenticate with OpenAI. In order of likelihood:
- No OpenAI key saved — your account is toggled to Codex but the OpenAI API key field is empty. Add one under Settings → Credentials.
- No Platform funds — the key is valid but the Platform wallet is empty. OpenAI reports this as
insufficient_quota(HTTP 429). This is the classic wallet trap: ChatGPT credit does not fund API keys — the balance must be on platform.openai.com → Settings → Billing, in the org/project that owns the key. See the OpenAI credential. - Key revoked or wrong project — regenerate the key and re-save it.
One sharp edge worth knowing: OpenAI's transport can surface all three of the above as a bare 401 Unauthorized, which looks like a bad key even when the key is fine and the real problem is billing. If you are sure the key is valid, check the Platform balance before rotating keys.
Codex runs show tokens but no cost
Not a bug. OpenAI's stream reports token usage but no dollar figure, and Tenbi does not invent prices — a wrong historical cost is worse than an honest blank. Codex run records show input/output token counts; dollar display arrives once Codex cost calibration exists. (Claude API-key runs are unaffected and keep showing real dollars.)