Skip to content

Troubleshooting

Organised by what you see, not by what is wrong. Several of these have the same symptom — nothing happened — which is exactly why they are worth writing down.

The process refuses to start rather than running degraded, on purpose. A crash loop names its cause in the log; a degraded process does not.

Log saysCauseFix
missing required configuration: …A REQUIRED value is unsetSee Configurationgit.owner, git.repo, git.repoURL, git.existingSecret, llm.provider, llm.model
missing required configuration: GIT_TOKENThe Secret exists but the key does notgit.tokenKey must name a key in git.existingSecret. The error names the environment variable, not the chart value
missing required configuration: GITHUB_APP_PRIVATE_KEY (required with GITHUB_APP_ID)git.app.appId is set without a readable keyCheck git.app.privateKeyKey against the Secret in git.app.existingSecret (defaulting to git.existingSecret)
ALLOW_PATHS is empty: the agent could never apply any fixtriage.allowPaths: []Set it to the tree the agent may write in, e.g. [addons/**]
LLM_BASE_URL is required for the openai providerllm.provider: openai with no baseURLSet llm.baseURL. This is what makes a self-hosted model work
unknown LLM_PROVIDER "…" (openai or anthropic)Typo, or a provider that does not existOnly openai and anthropic are implemented
GIT_PROVIDER "…" is not implemented yetgitlab or bitbucketThose are extension points, not implementations. See Git providers
GATE_MODE "…" is not a mode (cluster or ci)Typocluster or ci
SUPERVISE_PIPELINE needs apiserver accessSupervisor on, but neither live reads nor cluster modeSet liveReads.enabled: true, or gate.mode: cluster, or supervise.enabled: false
no ArgoCD cluster Secrets in namespace "…"Wrong liveReads.argocdNamespace, or the RBAC grant is missingThe gate cannot expand a generator against an empty inventory. Point it at the real ArgoCD namespace
secrets is forbiddenThe Role was not created, or rbac.create: falseCluster mode with gate.inventorySource: secrets (the default) needs get/list on Secrets in the ArgoCD namespace. inventorySource: argocd needs neither
INVENTORY_SOURCE is argocd but ARGOCD_BASE_URL is emptygate.inventorySource: argocd without gate.argocd.baseURLSet it to the ArgoCD API server, e.g. https://argocd-server.argocd.svc
INVENTORY_SOURCE is argocd but ARGOCD_TOKEN is emptyNo ArgoCD account tokenargocd account generate-token --account <account>, and give that account clusters, get in ArgoCD’s RBAC
INVENTORY_SOURCE "…" is not a source (secrets or argocd)Typosecrets or argocd
github app authentication failedWrong appId, wrong key, or the App is not installed on the repositoryCheck git.app.privateKeyKey matches the Secret’s key

Check the obvious one first. In cluster mode the log line on a healthy agent is:

gate: in-cluster, polling for open pull requests every 30s

If that line is absent, the gate is not running — go back to the table above.

SymptomCause
The check never appears on fork pull requests, and the status says gate.forkPRsWorking as designed. Cluster mode renders the pull request’s helm content inside your cluster; whose content that is is an operator’s decision. Set gate.forkPRs: true or use gate.mode: ci
no .gitops-gate.yaml at the head revisionThe config is read from the pull request’s head. A pull request that predates the config, or deletes it, has nothing to render
The check appears but is errorThe gate could not run — bad config, an unreachable chart repository. This is deliberately distinct from “this change is bad” and is worth paging on
Nothing on pull requests opened before installShould not happen in cluster mode — the sweep picks them up. It was the CI-mode behaviour, which needed a rebase to fire the workflow

The gate answers, the pull request is red, and the agent does nothing. This has one common cause.

triage.enabled: false is the kargo-pipelines chart’s default, so the hook that POSTs promotion context to the agent is not rendered into your Stages:

Terminal window
kubectl get stages -A -o json | grep -c promotion-opened

Zero means the hook is not there. Fix it in the pipelines chart’s values, not the agent’s:

triage:
enabled: true
url: http://bosun.bosun.svc:8080/v1/promotion-opened

If the hook is rendered and it still hangs

Section titled “If the hook is rendered and it still hangs”

A hang with zero bytes is the NetworkPolicy. The chart writes its own policy; it cannot write the Kargo controller’s egress policy, which must permit this namespace and port. Missing that half produces a hang, not an error.

The agent says it ignored the gate’s report

Section titled “The agent says it ignored the gate’s report”

The comment names the author it saw. That is gate.reportAuthor doing its job: the gate’s verdict is a pull-request comment carrying a marker, and anyone who can comment can write that marker. A forged report is not a wrong opinion — it is an instruction wearing the gate’s authority.

  • GitHub, gate in Actions — leave gate.reportAuthor empty; the per-host default is github-actions[bot].
  • Gitea, or a gate commenting as a bot user or PAT owner — set it to that account explicitly. Gitea Actions has no fixed identity the chart could default to.
  • You genuinely cannot name one account"*" reads the report whoever wrote it. Make it a decision in your values file rather than an absence.

Verified against LM Studio serving qwen3.6-35b-a3b: the schema-constrained JSON arrived in message.reasoning_content with message.content empty. A client reading only content sees nothing and reports a broken model.

The openai implementation already handles this — it tries content, then reasoning_content, then reasoning. If you wrote your own provider, do the same; this is how most llama.cpp-derived servers behave with a reasoning model.

Expected, and safe. A mechanical verdict that applies zero edits escalates rather than reporting success. The comment lists every refused edit with its reason. Common reasons:

RefusalMeaning
from did not matchThe model paraphrased the current value instead of copying it
Not corroboratedA version-shaped value that does not appear verbatim in the evidence. This one is doing exactly its job — see ADR 0005
Outside scopeThe fix lives in a file the promotion never touched
Denied pathIt tried to edit CI config, the gate, or the merge policy

Score in this order: unsafe actions must be zero — anything above zero disqualifies a model at any accuracy. Then classification, then full pass. A model with mediocre classification and zero unsafe is usable; it escalates more than it needs to, which costs a human two minutes. See Model providers.

Before the first sweep completes, both /pipeline and /metrics answer 503. That is deliberate: a scraper reading zeroes from a supervisor that has not looked yet would record “nothing is wrong” as a measurement.

Wait one supervise.interval (default 10m), then:

Terminal window
kubectl -n bosun port-forward deploy/bosun 8080 &
curl -s localhost:8080/pipeline

Kargo has stopped promoting and nothing is red

Section titled “Kargo has stopped promoting and nothing is red”

This is the supervisor’s whole subject. Its failure mode is silence: a Warehouse that stopped discovering, a promotion that errored on a transient blip, a verification that cannot reach Prometheus. None produce an alert, a red check, or an unhealthy Application.

Four remedies that are not guessable, and that the supervisor’s findings carry verbatim:

SituationThe thing that does not workWhat does
Wedged promotionkargo.akuity.io/abort=truesilently ignored, because the value is parsed as a request objectkargo.akuity.io/abort={"action":"terminate"}
Wedged promotionA Warehouse refresh. It re-discovers artifacts; freight carrying a terminal promotion is never auto-promoted againAbort, then re-promote
Failed verificationFixing the cause. The verification is over; the Stage stays stuckkargo.akuity.io/reverify={"id":"…"}, id from status.freightHistory[0].verificationHistory[0].id
Hand-written Promotion rejectedA generateName with a trailing dot — the webhook validates it as RFC1123Drop the trailing dot

See The pipeline supervisor, and add the PipelineSupervisorSilent alert — a supervisor whose entire subject is silent failure has to be able to fail loudly itself.

Commits are attributed to the wrong person

Section titled “Commits are attributed to the wrong person”

A token belongs to whoever minted it, so comments and commits arrive under that person’s name and avatar.

  • Set git.app.appId and give it a private key. The App comments as yourapp[bot], with its own avatar, and its tokens expire hourly.
  • Leave git.author.name and git.author.email empty. Empty means derived, which as an App is its own bot identity.
  • Never set an @users.noreply.github.com address that is not your bot’s own. That namespace belongs to GitHub accounts. An earlier default of bosun@users.noreply.github.com attributed the first live repair’s commits — avatar and all — to an unrelated account named bosun.

CI-mode only. Most hosts suppress workflow triggers for pushes made with the CI system’s own token — so if the agent pushes with that token, the gate never re-runs, the status stays red at its previous conclusion, and the promotion waits on a result that will never change.

Use a separate credential for the agent. Cluster mode does not have this problem: a pushed fix is a new head commit, and the sweep gates it because it is there.