LavaFlow

Have AI diagnose your project's issues and open draft pull requests that fix them.

LavaFlow uses AI to diagnose the issues your functions and frontends raise, from their logs and deployed source, and, when you allow it, opens a draft pull request with a fix in the project's connected repository. Volcano picks and runs the models, so there is no model or API key to set up.

Turn it on, and an issue starts a run that diagnoses it when it happens a second time, or when it comes back after you resolved it, within LavaFlow's run limits. Runs start on their own for exceptions your code throws or logs with console.error(err) or Python's logger.exception(). An error logged another way becomes an issue but doesn't start a run on its own, such as pino's or winston's error lines, a JSON log line you write, or a traceback you print; start one yourself. During the preview, LavaFlow needs the SUPERAGENT plan. A run also opens a draft pull request with the fix when:

  • the project has a connected GitHub repository;
  • a push to that repository built the deployment where the issue happened, or the deploy client reported a commit that Volcano verified with dirty: false (see Commit);
  • the Volcano GitHub App can write Contents and Pull requests in that repository (see Grant the GitHub App access).

Otherwise a run stops at the diagnosis and says why in pull_request_skipped.

curl -X PATCH "https://api.volcano.dev/projects/$PROJECT_ID/lavaflow" \
  -H "Authorization: Bearer $VOLCANO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"enabled": true}'
{
  "enabled": true,
  "max_stage": "pull_request",
  "auto_trigger": true,
  "enabled_repository": { "id": 1143663983, "full_name": "acme/shop" },
  "repository_changed": false,
  "plan_allows_lavaflow": true,
  "updated_at": "2026-10-13T12:20:41Z"
}

Check availability

LavaFlow is in preview and is not available in every environment. Check GET /capabilities: while lavaflow is available, you can turn LavaFlow on and start runs. Without it, both answer 404 with code feature_unavailable.

Where LavaFlow is not available, settings a project already saved and its runs stay readable, and you can still turn LavaFlow off or change settings that leave it off. A project that never saved settings gets 404 with code feature_unavailable.

During the preview, LavaFlow needs the SUPERAGENT plan. It is not available in local mode, where the endpoints answer 501.

Settings

FieldValuesDefault
enabledWhether Volcano diagnoses the project's issuesfalse
max_stagediagnose stops at a root-cause diagnosis. pull_request may also open a draft pull requestpull_request
auto_triggerWhether a recurring or regressed issue starts a run on its own. Otherwise runs start only when you asktrue

PATCH changes only the fields you send. A field you omit keeps its saved value, or takes its default when the project never saved settings. Turn LavaFlow on and off, and set max_stage or auto_trigger to stop at a diagnosis or start runs only on request, through this API or volcano-config.yaml.

A change also reaches runs in progress. Every run checks the settings and your plan again once the model finishes, before it records its result, so a diagnosis-only run is stopped too. A run that may open a pull request also checks before it asks for a fix, before each revision its review asks for, and before it opens the pull request. If LavaFlow is off by then, or the project is being deleted, the run ends canceled with lavaflow_disabled. If the plan no longer includes LavaFlow, it ends canceled with plan_required. Neither opens a pull request. If max_stage is diagnose by then, the run stops at its diagnosis and reports max_stage: diagnose. If an interrupted attempt already opened the run's pull request, the run keeps it and ends succeeded with it; it opens no other.

Read the settings back:

curl "https://api.volcano.dev/projects/$PROJECT_ID/lavaflow" \
  -H "Authorization: Bearer $VOLCANO_TOKEN"

A project that never saved settings returns the defaults.

When the project's repository changes

LavaFlow works only in the repository the project was connected to when a person turned it on. It reads source from that repository and opens pull requests there. The settings name it in enabled_repository:

{
  "enabled": true,
  "max_stage": "pull_request",
  "auto_trigger": true,
  "enabled_repository": { "id": 1143663983, "full_name": "acme/shop" },
  "repository_changed": false
}

When the project points at a different repository, or its repository is disconnected, repository_changed turns true and LavaFlow pauses: no run starts, and a run already queued diagnoses from the logs without reading source or opening a pull request. That includes connecting a repository after LavaFlow was turned on with none, when enabled_repository is absent. A rename or a transfer on GitHub keeps the repository, and full_name follows the new name.

To use the new repository, or to continue without one, turn LavaFlow on again. It records the repository the project points at now, if any:

curl -X PATCH "https://api.volcano.dev/projects/$PROJECT_ID/lavaflow" \
  -H "Authorization: Bearer $VOLCANO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"enabled": true}'

Re-applying volcano-config.yaml with enabled: true does not resume LavaFlow; the dry run and the apply say it stays paused.

Diagnose an issue

Start a run for an issue:

curl -X POST "https://api.volcano.dev/projects/$PROJECT_ID/issues/$ISSUE_ID/lavaflow-runs" \
  -H "Authorization: Bearer $VOLCANO_TOKEN"
{
  "id": "5f0e7c1a-3b9d-4e2f-8a6c-1d2e3f4a5b6c",
  "project_id": "8d2b5f3e-1a4c-4b6d-9e8f-0c1a2b3c4d5e",
  "trigger": {
    "type": "issue",
    "reason": "manual",
    "issue": {
      "id": "c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f",
      "title": "TypeError: Cannot read properties of undefined (reading <str>)"
    }
  },
  "status": "queued",
  "max_stage": "pull_request",
  "created_at": "2026-10-13T12:31:02Z"
}

trigger says what started the run. Every run today has type issue, and reason is manual, new_issue, or regression. Other kinds of trigger will carry their own fields, so treat a type you do not recognize as another kind of run rather than an error.

With auto_trigger on, a new issue starts a run on its own when it happens a second time, and an issue that comes back after you resolved it starts one right away, or an hour after the issue's last run if that was more recent. Only an open issue starts one: an issue you ignored or resolved does not, and a new issue you already ran yourself starts none. Only exceptions count: one your code threw, or one it logged with console.error(err) or Python's logger.exception(). An error logged any other way does not, even with its stack, such as console.error(JSON.stringify(entry)), an error line from pino, bunyan, or winston, or a Python traceback you print as text. Neither does a line that only reads like an error, such as console.log("request failed").

Poll the run until status is succeeded, failed, or canceled:

curl "https://api.volcano.dev/projects/$PROJECT_ID/lavaflow-runs/$RUN_ID" \
  -H "Authorization: Bearer $VOLCANO_TOKEN"
{
  "id": "5f0e7c1a-3b9d-4e2f-8a6c-1d2e3f4a5b6c",
  "status": "succeeded",
  "stage_reached": "diagnose",
  "commit_sha": "4b825dc642cb6eb9a060e54bf8d69288fbee4904",
  "commit_origin": "push",
  "diagnosis": {
    "summary": "Checkout reads the cart total before the cart loads.",
    "root_cause": "getCart in src/cart.js returns undefined for an expired session, and handler in src/checkout.js reads cart.total without a check.",
    "evidence": [
      {
        "kind": "log",
        "detail": "TypeError: Cannot read properties of undefined (reading 'total')",
        "location": "2026-10-13T12:29:14Z"
      },
      { "kind": "code", "detail": "cart.total read without a check", "location": "src/checkout.js:12" }
    ],
    "suggested_fix": "Return 404 from handler when getCart returns undefined.",
    "confidence": "high",
    "fixable": true,
    "files": ["src/checkout.js"]
  }
}

List a project's runs, newest first, with GET /projects/{id}/lavaflow-runs. Filter by issue_id or status, and page with limit and cursor.

A run reads the issue's runtime logs, within your plan's log retention. When a push to the project's connected repository built the deployment that was live when the issue was last seen, or that deployment recorded a verified commit with dirty: false, it also reads the source at that commit and reports it as commit_sha, even if a newer deployment is live now. commit_origin says which: push or client_reported. Otherwise it diagnoses from the logs alone and reports no commit_sha. With max_stage: pull_request, a run can also open a fix pull request.

Run limits

An issue has at most one queued or running run, and a project runs one at a time. In any 24 hours, manual and automatic runs together:

  • an issue starts at most 5 runs;
  • a project starts at most 200, and automatic runs take at most 150 of them, so you can still start runs while errors flood a project.

A start past a limit answers 429 with code issue_run_limit or project_run_limit, and a Retry-After header with the seconds until a run can start:

{
  "error": "LavaFlow ran this issue 5 times in the last 24 hours. Try again later.",
  "code": "issue_run_limit"
}

An automatic run past the issue's limit is skipped. One past the project's limit waits for room, and waiting runs start with the issue that happened most.

A run gets three attempts, and a pause doesn't use one. It retries when LavaFlow cannot reach its AI service or the run times out, and records why in failure_code meanwhile. If a fix was saved before an interruption, its retry uses the same repository and commit, even when a newer version has since deployed.

Why a run did not finish

statusfailure_codeMeans
queuedfeature_unavailableLavaFlow is temporarily unavailable. The run resumes automatically without using an attempt while paused
queuedfeature_pausedLavaFlow is paused. The run resumes automatically without using an attempt; a run paused with a fix opens its pull request then
failedmodel_request_rejectedLavaFlow's AI service rejected the request
failedmodel_unavailableLavaFlow couldn't reach its AI service on any attempt
failedtimed_outThe run did not finish in time on its last attempt
failedmodel_refusedLavaFlow declined to diagnose the issue
failedno_diagnosisLavaFlow finished without a diagnosis
failedbudget_exhaustedLavaFlow reached the run's limit before finding a diagnosis
failedattempts_exhaustedThe run did not finish within its attempts
failedinternal_errorLavaFlow could not run the diagnosis, or could not confirm your settings and plan
canceledlavaflow_disabledLavaFlow was turned off, or the project is being deleted, before the run finished
canceledplan_requiredThe plan no longer allows LavaFlow
canceledissue_not_foundThe issue no longer exists

An attempt saves its diagnosis before it opens the pull request, and when it can't confirm your settings and plan after the diagnosis. A retry diagnoses again. The run keeps the latest saved diagnosis and reports stage_reached: diagnose, even if a later attempt fails or the run is canceled; failure_code describes the last attempt. Such a run opens no pull request. A run that fails before any diagnosis has no stage_reached.

Older runs may also report codes no new run does: autofix_disabled (now lavaflow_disabled), and api_key_rejected, settings_changed, or managed_models_unavailable from before Volcano picked the models for every run.

Open a fix pull request

With max_stage: pull_request, a run that finds a fixable cause with medium or high confidence asks the model for the fix, checks it, has it reviewed, and opens a draft pull request into the project's production branch:

{
  "id": "5f0e7c1a-3b9d-4e2f-8a6c-1d2e3f4a5b6c",
  "status": "succeeded",
  "max_stage": "pull_request",
  "stage_reached": "pull_request",
  "commit_sha": "4b825dc642cb6eb9a060e54bf8d69288fbee4904",
  "commit_origin": "push",
  "patch": {
    "title": "Return 404 when the session's cart is gone",
    "edits": [
      {
        "path": "src/checkout.js",
        "old_text": "  const total = cart.total;",
        "new_text": "  if (!cart) return new Response(\"Cart not found\", { status: 404 });\n  const total = cart.total;"
      }
    ]
  },
  "pull_request": {
    "number": 42,
    "url": "https://github.com/acme/shop/pull/42",
    "branch": "volcano/lavaflow/5f0e7c1a-3b9d-4e2f-8a6c-1d2e3f4a5b6c",
    "state": "open"
  }
}

Before the pull request opens, separate reviewers check the fix. When a reviewer finds a problem, the model revises the fix and the reviewers look again, for a few rounds. A fix whose review does not pass still opens, with a warning near the top of its description that says why and asks a person to review it before merging. The run's patch is the fix that opened, which may be a revision of the first one.

The pull request:

  • Is cut from the deployed commit the run read, so it shows only the fix, even when the production branch has moved on.
  • Is opened by the Volcano GitHub App, not by you, on a branch named volcano/lavaflow/<run id>. Repositories whose GitHub plan has no draft pull requests get a regular one.
  • Has a commit message ending in [skip ci], so GitHub Actions and most CI services do not run model-written code before someone reviews it. Push a commit to the branch to run CI.
  • Describes the fix according to the repository's visibility. On a public repository, the description names the resource, prints the run ID, and links to the Volcano dashboard. The diagnosis stays with the run there, where only the project's members can read it. The review's findings are not shown, but a fix whose review did not pass still says so. On a private repository, the description explains the diagnosis, evidence, and review after removing credentials, email addresses, quoted values, identifiers, addresses, and numbers the model or its reviewers may have copied from your runtime logs. A description too long for GitHub lists fewer items; if it still does not fit, it links to the dashboard as on a public repository. The title and commit message have the same values removed on every repository. The change is model-written and visible to anyone who can read the repository.
  • Says so when its base is a commit your deploy client reported rather than one a push deployed. See Commit.

Volcano never merges it. A fix never changes CI, Git, editor, or workspace configuration (.github/, .gitlab-ci.yml, .husky/, .vscode/, .devcontainer.json, .gitpod.yml, and similar), lockfiles, or .env files in any letter case, and changes at most 10 files. It also never changes, in any directory, volcano-config.yaml, CODEOWNERS, .gitattributes, .gitmodules, or dependency and merge bot configuration (Dependabot; Renovate's renovate.json, renovate.jsonc, renovate.json5 and .renovaterc* files and the renovate key in package.json; Mergify, Snyk, PyUp). A change to a package.json must leave it a JSON object. A fix repairs a package.json that isn't valid JSON only when the file doesn't mention renovate and has no backslash escapes. An issue has at most one open fix pull request. A merged fix counts as open until it deploys, for up to a week.

When you merge

Merging the pull request does not resolve the issue on its own: the old code is still live until the merge deploys. Once a deployment that includes the merge commit goes live, Volcano resolves the issue as of that moment and sets pull_request.deployed_at. Errors from the old code before then do not count against the fix; an error after that reopens the issue. If the error already happened again on the new deployment by the time Volcano checks, the issue stays open.

A deployment includes the merge when it was built from the merge commit or a later commit that contains it, from the repository the fix merged in. A redeploy of such a build, after a variable change for example, counts too. Volcano asks GitHub, so it works with merge commits, squash and rebase merges, and merge queues alike. If more than 20 deployments went live between the merge and Volcano's check, it resolves the issue as of the merge instead, so any error since keeps the issue open, and deployed_at is the merge time.

Closing the pull request without merging sets its state to closed. The issue stays open, and the next run may propose another fix. Reopening it sets state back to open, unless a newer fix for the issue is open. While a run for the issue is queued or in progress, the reopen is recorded within an hour after it finishes. Connecting the project to another repository records its open fix pull requests as closed, and a merged fix stops waiting for its deploy, since neither can deploy from the new repository.

Grant the GitHub App access

Opening pull requests needs the Volcano GitHub App's Contents and Pull requests write permissions on the connected repository. When GitHub asks the installation's owner to approve updated permissions, approve them in the installation settings on GitHub. Until then, runs diagnose and report permission_missing.

The project owner's connected GitHub account must also be able to push to the repository. The App opens pull requests only where the owner could.

Why a run opened no pull request

A run whose max_stage is pull_request still succeeds with its diagnosis when it opens no pull request, and says why in pull_request_skipped:

pull_request_skippedMeans
source_unavailableThe project has no connected repository, the deployment came neither from a push to it nor with a verified clean commit, or the source could not be read
pull_request_openA fix pull request for this issue is still open, or merged and not yet deployed
not_fixableThe cause is not a code change in this repository
low_confidenceThe diagnosis is not confident enough to propose a fix
no_patchThe model gave no fix that passed Volcano's checks
permission_missingThe GitHub App installation lacks the permissions above or access to the repository, or the project owner cannot push to it
repository_changedThe project points at a different repository, or none, than when the run started or LavaFlow was turned on
publish_failedGitHub refused the pull request, or was unavailable

When a valid fix could not be published, the run keeps it in patch so you can apply it yourself.

Who can change LavaFlow and start runs

Any project credential can read the settings and runs, including read-only project access tokens. Only a person changes the settings or starts a run, with a platform token: LavaFlow opens pull requests in your repository, so a project access token is refused with 403. The same applies to the lavaflow section of volcano-config.yaml: a project access token can re-apply it unchanged but cannot change it.

Agents connected through the MCP server get the reads: get_lavaflow_settings, list_lavaflow_runs, and get_lavaflow_run.

Plans

During the preview, turning LavaFlow on needs the SUPERAGENT plan, and nothing is charged yet. On HOBBY, saving "enabled": true answers 403, and plan_allows_lavaflow is false. Other saves, including turning LavaFlow off, need no plan. If your plan cannot be resolved, they still succeed and leave out plan_allows_lavaflow.

Settings that are on when an account moves to HOBBY are parked: they stay stored, and saving them again is not refused, but no run starts. They apply again when you upgrade. See Plans and limits.

What a run sends to the model

A run sends the model the issue, excerpts of the matching runtime logs, and source files from the project's connected repository at the commit that was live when the error happened. Log excerpts can contain values your code logged.

Log lines can carry text your application's users wrote, and the model reads them. Review every fix pull request as you would a contributor's: the checks above limit what a fix can touch, but they cannot tell a correct change from a wrong one.

Errors

StatusCodeWhen
400A field is invalid or unknown, or a PATCH names no field
403plan_requiredThe plan does not include LavaFlow
403A project access token tried to change the settings or start a run
404feature_unavailableLavaFlow is not available in this environment, and the request saves a change that leaves it on (such as {"max_stage": "diagnose"} while it is on), is a project's first save, starts a run, or reads a project that never saved settings
404The project, issue, or run does not exist, or belongs to someone else
409Starting a run while LavaFlow is off, or while the issue already has a queued or running run
409repository_changedStarting a run while the project points at a different repository than LavaFlow was turned on for, or none. Turn LavaFlow on again
429issue_run_limitThe issue started 5 runs in the last 24 hours. Retry-After says when a run can start
429project_run_limitThe project started 200 runs in the last 24 hours. Retry-After says when a run can start
501Local mode
503Your plan could not be resolved while reading the settings, saving a change that leaves LavaFlow on, or starting a run. Nothing was saved. Retry later

On this page