Troubleshooting
Run the self test first. It is faster than this page and it knows which hop failed. Come here to interpret what it told you.
This page is indexed by symptom. For the jobs that have a right order — proving the egress, verifying a credential before you flip a source live, rotating one — see Procedures.
Install-time
agent-runner and synth-seed show Init:0/1 for the first seconds of a
fresh install with the bundled Postgres — expected, and it is the install
holding them back on purpose. Both workloads run their schema migration at boot,
and from 0.14.0 each carries an init container, wait-for-postgres, that polls
the bundled instance until it accepts connections and only then lets the main
container start. On 0.13.x and earlier there was no gate, and the same two
workloads instead restarted once or twice while Postgres came up — if that is
what you are looking at, the rest of this entry still describes it.
What a normal first minute looks like on 0.14.0 with the bundled Postgres:
NAME READY STATUS RESTARTS AGE
prism-postgres-0 0/1 Running 0 10s
prism-agent-runner-... 0/1 Init:0/1 0 10s
prism-synth-seed-... 0/1 Init:0/1 0 10s
then Postgres turns 1/1, the two Init:0/1 pods go PodInitializing and
Running within a couple of seconds of it, and synth-seed ends Completed.
RESTARTS stays at 0 on every pod. Measured on 0.13.0, Postgres takes about 14
seconds from pod start to Ready on an unremarkable node, and the gate waits up
to postgres.startupWaitSeconds (180 by default) for a slower one. A Postgres
pod that stays 0/1 Running well past that with Readiness probe failed
events is a different thing from a slow one, and the entry below on the two
refused-connection errors says how to tell them apart. Init:0/1
can last longer than Postgres does, though: the init container runs from the
Postgres image, the largest in the release, and on a multi-node cluster the
runner's node and the seed's node each have to pull it before the wait starts.
That pull is outside the bound, and a registry problem during it shows as
Init:ImagePullBackOff on these two pods. Nothing else waits: the app,
Postgres and the MCP servers each start once and do not depend on the gate.
The init container's own log says what it saw:
kubectl logs -n <namespace> <pod> -c wait-for-postgres
postgres at prism-postgres:5432 is accepting connections after 12s
On OpenShift with the default postgres.bundled: true, agent-runner and
synth-seed do not appear at all — no pod, and the install ends with the
post-install hook timing out. The init container has to run as the Postgres
image's own user (UID 999), and the restricted-v2 SCC refuses a UID outside
the range it assigned the namespace, so the ReplicaSet and the Job are refused
admission; kubectl describe replicaset and the Job's events carry the
reason. It is the same root cause as the bundled Postgres not starting there:
set postgres.bundled: false and point at a managed instance, as install step
1 says, and no gate is rendered.
If you run an external database (postgres.bundled=false), there is no gate
and a refused connection will not converge — the database is not something
this install is starting, so there is nothing to wait for, and a wait would only
postpone the error below. The gate also could not run there: it uses the
Postgres image, whose default user is root, so it has to pin runAsUser to that
image's postgres user, and OpenShift's restricted-v2 SCC refuses a UID
outside the range it assigns the namespace — the same reason the bundled
Postgres itself cannot start on OpenShift, and the platform where an external
database is the only option. Check that the
instance is reachable from the namespace (firewall rule, private endpoint,
network policy) and that postgres.external.host, .port, .database and the
credential are right. Left alone, agent-runner crash-loops indefinitely and
synth-seed exhausts its six retries and fails the post-install hook.
The two workloads report a refusal in different languages, so match the one
you are looking at. In agent-runner's previous container you will find:
kubectl logs -n <namespace> <pod> --previous
Error: connect ECONNREFUSED <postgres-ip>:5432
at async applySchema (/app/packages/db/src/migrate.ts:72:3)
at async main (/app/apps/server/src/index.ts:55:3)
synth-seed is Python and says the same thing differently — a traceback ending:
ConnectionRefusedError: [Errno 111] Connect call failed ('<postgres-ip>', 5432)
When you see those two errors on 0.14.0 with the bundled Postgres on a fresh
install, the gate gave up: Postgres did not accept connections within
postgres.startupWaitSeconds, and the init container stood aside so the
failure would be the familiar one above rather than a pod parked in Init:0/1
with no explanation. Its log ends with did not accept connections within 180s ... starting anyway. The question is then why the bundled Postgres is slow or
absent, and the answer is on its pod, not on the two that are restarting:
kubectl describe pod <release>-postgres-0 names an unbound volume, an image
that will not pull, or a quota. It can also show a server that is up while
the probe is what fails: Readiness probe failed: command timed out events
on a pod whose own log already says listening on IPv4 address "0.0.0.0", port 5432 mean Postgres is running under a CPU cap too tight for pg_isready to
answer inside postgres.readinessProbe.timeoutSeconds (5 by default; on 0.13.x
the chart set no timeout and Kubernetes applied 1 second, and an install capped
at 50m CPU never met it), so the pod is never Ready, the Service has no
endpoints, and every client is refused by a ClusterIP in front of a server that
would answer — raise the timeout or the cap. The restarts converge on their own once
Postgres does listen, exactly as they did on 0.13.x. If Postgres is merely slow
on your node — a first boot has to bind a volume, pull the image and run
initdb — raise postgres.startupWaitSeconds and the churn goes away on the
next install; above about four minutes raise helm install --timeout with it
(5 minutes by default), because the synth-seed Job is a post-install hook and
helm gives up on the hook before the gate would. The same two errors on an
install that was running fine mean Postgres went away later — a restart, a node
change, an upgrade rolling the StatefulSet — and the init container is no help
there: init containers do not re-run when the main container restarts, so its
log still reads is accepting connections, and the entry for a Postgres that
will not start is further down.
On 0.13.x, expect one or two restarts of agent-runner and synth-seed,
settling inside a minute. The count is not a fixed figure — it is however many
attempts land before Postgres starts listening, so two installs of the same
version on the same cluster can differ; a rehearsal install of 0.13.0-rc0 took
two and three restarts and was equally healthy. STATUS may read Error or
CrashLoopBackOff while the restart backoff runs. Once every pod reports Ready
and the synth-seed Job reads Completed, the RESTARTS counts stop climbing
and stay at the number they reached; a non-zero count on a settled 0.13.x
install is a record of those first seconds, not something to clear.
When to start worrying, on any version: counts still climbing after a few
minutes, or an error in --previous that is neither of the two above. A genuine
fault gives a different error, and the entries below name them. If pods are
Pending rather than restarting or initialising you are not looking at this at
all; kubectl get events -n <namespace> will name a scheduling or quota reason
instead.
Two reactions cost more than the problem, and both are common on a first
install: raising a support case for an install that is in the middle of
converging, and changing things underneath it — editing values, deleting pods,
re-running helm upgrade. Neither makes it settle sooner, and the second
restarts the clock.
agent-runner CrashLoopBackOff, logs show a vector extension error — your
Postgres refused the CREATE EXTENSION IF NOT EXISTS vector the runner's boot
migration runs, and the runner has no way around it. What to do depends on the
provider: Azure flexible server refuses until VECTOR is in the
azure.extensions server parameter, while Cloud SQL needs nothing and this
error means something else — the connecting role lacking the privilege, or the
wrong database. Install step 1a has the one psql line that tells you which.
Every chat answers "no answer from the agent", and the runner logs private outbound URLs are disabled — you are on 0.4.0 or 0.4.0-rc0. The runner
refuses to fetch URLs that resolve to private addresses, and the MCP server
addresses the chart generates are in-cluster services, so the very first turn
fails while registering them. The self test says it precisely:
Could not register MCP profile 'Agent Router Usage' (HTTP 500). Upgrading to
0.5.0 fixes it and needs no values change. If you cannot upgrade yet:
kubectl set env deploy/<release>-agent-runner \
RUNNER_ALLOW_PRIVATE_OUTBOUND_URLS=1 -n <namespace>
That is a stopgap, and the reason to move it is legibility rather than
durability. A kubectl set env variable does survive a routine helm upgrade
— Helm preserves fields no chart version manages — but it does not survive
helm uninstall and reinstall, helm upgrade --force, or anything else that
recreates the Deployment, and a later chart version that renders the same
variable overrides it silently. It is also in no values file, so nobody
reviewing your configuration can see that it is load-bearing. From 0.5.0,
anything of this kind belongs in agentRunner.extraEnv in your values file
instead, where it is both visible and durable.
helm upgrade refuses with router.platformUrl is empty or router.proxyUrl is empty — working as intended, and cheaper than the alternative. Both
Router URLs are required and neither has a sensible empty behaviour, so the
chart refuses to render one rather than install it. router.platformUrl is
your tenant's management-plane URL from your Router console, and it is required
even under router.keyMode: byok, where no admin key is needed — the runner
derives its Router hosts from it before it looks at any credential.
router.proxyUrl is the data-plane gateway for the project in app.projectId,
and it does not fall back to router.platformUrl. See install step 4.
On a chart before 0.6.0 there is no render error and the symptoms arrive
later: an empty router.platformUrl is agent-runner CrashLoopBackOff logging
TARE_PLATFORM_URL is not set, and an empty router.proxyUrl is a healthy
install where every question fails, with the self test's inference endpoint
line skipped as not configured. Set the value and helm upgrade.
helm upgrade refuses with app.projectId is empty — also working as
intended, and the state it refuses is the quietest failure this chart had. The
value is not required to be any particular thing: default is a real Router
project and is the chart default, so an install that never touches this key is
fine. Only "" is refused, and only because values.yaml sets the key
explicitly, which makes an empty value a deliberate override rather than an
omission. Set the Router project this install serves — the same project your
inference key and router.proxyUrl belong to.
On a chart before 0.6.0 an empty app.projectId installed, and became
default: the app's PROJECT_ID and the runner's allowedProjectIds both. The
two agreed, every pod was healthy, the render was clean, and every question
came back 403 from the gateway — because on a Router whose projects have
their own gateways, default is the one project with no data plane behind it.
It presents exactly like a bad credential, so the search goes to the key, the
tenant, the Secret and the egress policy, and the wrong value is one that
appears in no values file. If you are on an older chart and seeing that 403,
check kubectl exec deploy/<release>-app -- printenv PROJECT_ID against the
project your inference key was issued for before you touch anything else.
helm upgrade refuses with sources.github is "Live" — also working as
intended, and usually one keystroke to fix. Source modes are matched exactly
and in lower case: sources.github, sources.jira and sources.directory
take synthetic or live, sources.spend takes synthetic, indexed or
live, and indexed means nothing on the other three. The message names the
key and the value as you typed it, so read that value against the list —
Live, LIVE and a misspelling all land here, and so does an empty value,
which names no mode at all. The check runs before anything is applied, because
every component in the chart is handed the same four values and they have to
agree.
On a chart before 0.7.0 a capitalised mode was refused by nothing at all, and
the install was the quiet kind: the chart rendered no ingest for that source —
no backfill, no schedule, not even the pre-flight check — while the app
lowercased the same string and served the source as live, so the mirror was
empty on install and stayed empty. Where it surfaced was the self test, as a
red live but 0 rows line that reads as a data problem rather than as a typo.
An empty or misspelt mode got as far as the synthetic seed job, which exited
with SOURCE_JIRA must be one of 'synthetic' / 'live', got '' after the
upgrade had applied everything. If a source is unaccountably empty on an older
chart, check the mode as spelt — helm get values <release> -n <namespace> —
before you go looking at credentials.
Every chat turn fails with Agent 'prism' already exists but is not reachable by 'prism-provisioner' — the install predates the change that gave Prism a
fixed owner for the agent everybody shares, and the upgrade cannot migrate the
row it left behind: the agent is owned by an identity the app can no longer act
as, and its name is unique, so it can be neither used nor replaced. Nothing
else is wrong and no further upgrade fixes it. It is a one-off repair against
the runner database, with a script that ships in the chart tarball —
Re-own the shared agent is the procedure, and its dry
run confirms the diagnosis before anything is written.
Pods rejected with must specify limits.cpu — the namespace quota requires
every container to declare CPU limits, and something is not declaring them. The
chart sets them on every container, so this is an override that emptied a
resources block — put the limits back. (Before 0.13.0 the bundled Postgres
declared none of its own and this was the chart's fault, not yours; on that
release the pod needs either a LimitRange in the namespace or an upgrade.)
If it is the bundled Postgres that will not start, and it was running until
something restarted it, the cause is usually a quota added after Prism was
installed. A ResourceQuota is enforced when a pod is created, not while it
runs, so a datapond started before the quota keeps running and cannot be
replaced — and the failure appears at a node drain or a rescheduled pod rather
than at the upgrade that introduced it. Upgrading to 0.13.0 or later gives the
pod its own limits; until then, a LimitRange in the namespace supplies a
default and lets it be recreated.
Pods rejected with exceeded quota — a different failure with a similar
shape, and the important thing is which limit it names:
Error creating: pods "prism-app-..." is forbidden: exceeded quota:
requested: pods=1, used: pods=10, limited: pods=10
Error creating: pods "prism-synth-seed-..." is forbidden: exceeded quota:
requested: limits.cpu=500m, used: limits.cpu=3750m, limited: limits.cpu=4
A ResourceQuota caps pods, CPU and memory independently, so raising the
pod count can leave you refused a minute later on limits.cpu with a message
that looks like the same problem. Jobs retry, so the CPU form repeats for the
same Job and the pod count appears nowhere in it.
Size against the upgrade rather than the steady state — the synthetic-seed hook runs while the ingest jobs are still going, so the peak is roughly double. Compute, and the namespace quota you need carries the per-component figures, generated from the chart, and what to leave room for.
Image pull failures — global.imageRegistry must include the repository
prefix the images were imported under, and your image pull secret must exist in
the namespace.
Postgres connection failures that look like a DNS or host problem — check
the password in runner-database-url is percent-encoded. An @ or / in
the password produces a DSN that parses to the wrong host, and the error you get
back describes the wrong host rather than the real fault.
agent-runner failing its /health probe, with no logs at all — this is
almost never a probe or a networking fault. The runner dies inside its boot
migration, before it writes anything to stdout, so the running pod's logs are
empty and only the previous container has the evidence:
kubectl logs -n <namespace> deploy/<release>-agent-runner --previous
An UNABLE_TO_VERIFY_LEAF_SIGNATURE there means TLS to Postgres, not the probe.
The runner is the one component that is not a libpq client, and it needs
uselibpqcompat=true in runner-database-url to read sslmode the way every
other component does. Without it, sslmode=require means verify the
certificate chain to the runner alone — which fails against any Postgres whose
certificate is not chained to a publicly-trusted root (Cloud SQL, or any
private-CA instance), while the same value works everywhere else. Add the flag
to the DSN and restart. If you build the Secret yourself (existingSecret),
this is the step most likely to have been missed; see install step 3.
Do not reach for sslmode=no-verify to get past it. It is a node-postgres
spelling that libpq does not accept, so it trades a broken runner for a broken
everything-else: invalid sslmode value: "no-verify" from the app, the MCP
servers, ingest and the seed job.
root certificate file "/root/.postgresql/root.crt" does not exist — the
install is on sslmode=verify-ca or verify-full without a CA bundle the pods
can read. Both client families need one and neither ships with it. Either
supply the CA, or use sslmode=require — which still encrypts, but does not
verify the server certificate.
prism-app CrashLoopBackOff with LOCAL_AUTH=1 but the install cannot sign anyone in — local sign-in is on and something it cannot work without is
missing; the log line names every missing value (SMTP host, sender, base URL,
super admins, Postgres). This is deliberate: a pod that came up anyway would
serve a sign-in page whose emails go nowhere. Fix the named values and
helm upgrade. The same message right after an install can also mean Postgres
simply isn't up yet — the app fails fast and Kubernetes retries until it is.
Local sign-in
The magic link never arrives — three places to look, in order. (1) The
self test's smtp relay line: it asks the relay whether it would deliver to
each bootstrap admin, per address, without sending mail. (2) The app's
app_events table (or its log): a refused request is recorded — an address
not on the allowlist, a rate-limited address (max 3 links per address per 15
minutes), or a delivery failure (auth_email_failed, with the relay's error).
The sign-in page deliberately says "check your email" in every one of those
cases; the operator's records are where the truth is. (3) Spam. Corporate
mail scanners that pre-fetch links are not on this list: opening a link
shows a confirmation page, and only its "Continue to Prism" button (a form
POST, which scanners never submit) spends the single-use token — so a
scanned link still works when the person clicks it.
auth_email_failed (or the smtp relay line) reports a certificate
verification error — the relay presented a TLS certificate the pod cannot
verify, typically one issued by a private CA. Prism verifies the relay's
certificate — chain and hostname — on both TLS modes, by default. The right
fix is at the relay: a certificate from a CA the pod trusts, matching the
hostname in smtp.host. The last resort is smtp.tlsVerify: false, which
keeps TLS but trusts whoever answers — accept that knowingly or not at all.
(A relay with no TLS is smtp.tls: none, not an unverified handshake.)
auth_email_failed reports The handshake operation timed out — a
different fault from the one above, with a different fix. The relay answered
and advertised STARTTLS, and then the TLS negotiation itself stalled until the
30-second timeout. Verification never got the chance to run, so
smtp.tlsVerify: false will not touch it. Two causes account for nearly all of
these: something in the path inspecting SMTP and mangling the negotiation,
which enterprise firewalls do, or a port that does not terminate TLS the way
its banner claims. Try implicit TLS — smtp.port: 465, smtp.tls: implicit — which is encrypted from the first byte and so offers no cleartext
negotiation to interfere with. Expect the failure to be intermittent rather
than absolute: the same relay can complete one handshake and stall the next.
One colleague receiving their link is therefore not evidence that another's
failure is about their address — nothing in the send path varies by recipient.
smtp.tls: none also clears the error, and is the wrong reflex unless the
relay is in-cluster: it sends sign-in links, which are bearer credentials for
the account, in the clear to anyone on the path. Reach for it only where that
traffic never leaves the node network, and treat it as temporary anywhere else.
Locked out — no administrator can sign in — add an email to
app.auth.superAdmins and helm upgrade: seeding is additive, so the next
boot creates that admin without touching anything else. One sharp edge: the
address must be one with no existing allowlist row — seeding never
touches an existing row, so a listed address that was demoted in-app stays
demoted (the skipped seed is logged as seed_skipped_existing, which is why
an upgrade can report having added nobody). Use a fresh address. This needs
cluster access, not a Prism login — that is what makes it break-glass. For a
report while locked out, the self-test CLI (see self-test.md)
runs via kubectl exec with no sign-in at all.
The model path
Chat errors, runner logs cannot obtain an inference key — the Secret has
neither tare-inference-key nor tare-platform-api-key. Put your
project-scoped inference key in tare-inference-key (install steps 1c and 3).
Setting valet-api-key does not satisfy this and is not meant to — it is the
Router management credential, read by different components for a different
purpose (which credential buys what).
Chat returns 403 "not authorized for this data plane" — the inference
key's project doesn't match the gateway. app.projectId, router.proxyUrl and
tare-inference-key must all name the same project (install step 1d). Read the
project the app is actually presenting rather than the one you meant to set —
kubectl exec deploy/<release>-app -- printenv PROJECT_ID — and if it says
default on a Router whose projects have their own gateways, that is the
answer: before 0.6.0 an empty app.projectId became default silently. See
the install-time entry above.
Every question fails, but everything else looks healthy — pods running,
database connected, MCP servers answering. This is almost always the model:
Prism is configured to call a model your gateway does not serve. The self test's
model catalog line confirms it in one look. See model.md.
The self test says model catalog cannot tell whether the gateway
answered — the agent runner image predates the catalogue reporting this check
relies on. Upgrade to a matched set of images; the warning is the check
declining to guess rather than a fault in your environment.
Data
A GitLab source shows rows up to one date and never advances, and the ingest
log repeats transport error (ReadTimeout) six times — on 0.10.1 and earlier,
the sync's read timeout was a fixed 30 seconds and GitLab answers a page it
cannot finish at about 32. Upgrade to 0.10.2, which waits longer than GitLab
does (ingest.engine.httpTimeoutSeconds, 120 by default) and keeps the page
GitLab sends back with the diff sizes empty rather than giving up. On 0.10.2 the
same line names the endpoint, how long it waited and the value to raise; a page
that comes back with errors and nothing else is named by its cursor and the
field GitLab timed out on, and the run goes on to the next group before it
reports the failure. If the same cursor is named run after run, GitLab is
answering that page with no merge requests at all; send us the line — the
remedy is a smaller page, which is a change to the source's document. See
How Prism reads GitLab.
One GitLab group never answers and the other groups stop filling too —
before 0.10.2 the first group that could not be fetched ended the run, and the
groups after it in the list were never walked. From 0.10.2 every group is
walked, the one that could not be fetched is named in the failure with what it
answered, and the others' rows are in the mirror. The run is still reported as
failed, and a failed source is retried hourly rather than on its schedule: each
retry re-walks the whole window for every group (the rows are refreshed, not
duplicated) and waits about six minutes on a group that never answers. That
continues until the group answers or is removed from the source's groups.
Removing it is a coverage decision, not a filter (see The groups you list are
a coverage decision).
A source shows live but 0 rows — synthetic data evicted, none ingested —
the first live ingest truncates that source's synthetic rows before writing real
ones. It truncated, then wrote nothing. Your credential or network path to that
source is the place to look, not Prism. The ingest job's logs say which.
This state is now preventable, and the prevention is a helm upgrade you were
going to run anyway: configure the credential and target, upgrade without
flipping the source, and the self test reports whether that upstream is ready
before the flip evicts anything. See
Take a source live.
Reaching this line means the flip happened first — the same assertions diagnose
it after the fact, and a credential that authenticates cleanly and finds nothing
is the usual cause.
On GitHub, read org_not_visible and org_visible_but_no_prs_found as token
policy before you read them as network. A fine-grained personal access
token has two ways to produce them that no firewall is involved in: the
organisation requires approval for fine-grained tokens and nobody has approved
this one — it authenticates, and keeps only the read-only access to public
resources every such token carries — or the organisation blocks them outright.
Both verdicts look identical to an egress rule that has not landed, so check the
organisation's personal access token settings first; it is much the cheaper of
the two to check. Note that this only reaches the verdict where the
organisation's repositories are private throughout: where some are public, the
same unapproved token sees those, the probe passes, and the symptom is the low
figures below rather than a verdict.
What the GitHub credential needs
is the full list, for both token types.
GitHub figures are plausible but low, and nothing anywhere is red — the
likeliest cause is a GitHub token that cannot see every repository: one scoped
to Only select repositories rather than to all of them (re-scoping it to
All repositories is the fix, and it needs no change of credential kind —
see Widen a source's
scope), or a fine-grained
token awaiting an organisation owner's approval, which retains access to the
organisation's public repositories and nothing else. There is no fault to
find in either case: the walk succeeded, the
pre-flight passed, and the repositories the token cannot reach were never
fetched, so every org-wide total is a subtotal — and Prism labels it
org-wide (every repo in the index), because the index is all it can see.
Compare the repositories in the answer against the ones you expected, then
compare the token's repository access — and its approval state — against the
same list. Widening the token
fixes it from the next sweep onwards, and only for the days that sweep covers:
the newly-reachable repositories' history arrives only when those day buckets
are walked again. Widen a source's scope is the procedure for
both halves. Do not reach for the DELETE FROM ingest_state recipe further
down this page to force it — it is written for a different problem, and it
makes the next run a first run, which truncates the table and re-walks only
ingest.backfillDays. The re-sweep on that page is non-destructive, and it is
not worth trading years of history for a few repositories' worth of it.
GitHub reports unrefreshable_installation_token — the credential in
github-token is a GitHub App installation access token, the ghs_… string
GitHub hands back when you exchange a JWT. It works at this moment and it
expires one hour after it was minted; Prism cannot refresh one it was simply
handed, so the source would run, look healthy, and then start failing with
bad_credential — which reads as somebody having revoked it. The pre-flight
catches it deliberately rather than passing, because an hour-late failure with a
misleading name is worse than a red check now.
The fix is to give Prism the App's identity and key instead of a token it minted
for you, so it can mint its own: ingest.github.app.appId,
ingest.github.app.installationId and secrets.githubAppPrivateKey. Widen a
source's
scope
is the procedure. The verdict already tells you how many repositories that
installation reaches, so you can see what you are about to gain.
GitHub reports bad_app_credential — the App is configured and cannot be
used as configured, and the verdict's error says which of the three values is
wrong. The three shapes it distinguishes, because they have different fixes:
- half-configured — one or two of the three set. Prism refuses rather than
falling back to
secrets.githubToken, which would ingest under a credential you did not choose; - the private key does not match the App, or the pod's clock is more than a few minutes out — the JWT Prism signs is valid for nine minutes and a drifted clock puts every one of them outside its own window;
- no such installation —
installationIdis the installation's id, the number at the end of the App's Configure URL on your organisation, and not the App's id.
A source shows ingest stalled — last completed …, past the Nh budget — the
rows are there and are being served, but nothing has swept that source for
several of its own scheduled intervals. The ingest is the place to look, not the
app — but since 0.15.0 a failing source does not make the ingest Job fail,
so a green Job here does not mean the source is fine.
Start with the diagnostic bundle, not
with kubectl. Its ingest block has an entry per source with
last_failure.at and last_failure.error — when it failed and why, in full,
for every source including the ones you registered yourself.
If you do go to the pod logs, search for the source's own name rather than the
tick's done line. A failed scheduled source backs off for up to an hour
while the tick runs every few minutes, so the done line you find will usually
belong to a later, clean tick and read failed: []. The source's own line on
those ticks says not_due — and carries the reason after it. Only the tick
where it actually failed carries it on done, and
successfulJobsHistoryLimit: 1 means that pod is replaced by the next tick's.
An uploaded file — a roster, or any kind: file source — has no retry
backoff, so it is re-attempted every tick and stays in failed on every done
line until the file is corrected. Searching for its name works either way, which
is why that is the instruction.
kubectl logs -n <namespace> -l app.kubernetes.io/component=ingest-tick --tail=200 \
| grep '<source name>'
kubectl get jobs -n <namespace> still finds a run stuck Running, and a
failed Job now means the tick itself could not run, or a source failed and
Prism could not write down why. The usual causes are a credential that has been
revoked since go-live, an egress rule that changed, and a node or quota problem
that stops the CronJob creating pods at all. Answers keep working throughout and
stay labelled with the coverage they actually have — the figures are not wrong,
they are just not moving. ingest.stallAfterIntervals is the multiple the
budget is built from, and ingest.github.schedule / ingest.jira.schedule the
interval it multiplies.
A GitHub ingest Job failed with the table is empty … there is a watermark
— GitHub has ingested here before and the run has just ended with
pull_requests holding no rows at all, so it refused to record itself as a
successful sweep. Note the gate is on the table being empty, not on the run
having fetched nothing: a quiet weekend fetches nothing, leaves the rows that
are already there, and passes. Same causes as the stall above, caught one run
earlier. It is deliberately not fatal on the very first run: a first ingest
against a genuinely empty window is indistinguishable from a broken one, so the
first run is allowed to succeed and the next firing is the one that fails. The
Jira ingest has no equivalent gate; a Jira mirror that ends up empty shows up as
live but 0 rows on the self test instead.
An ingest Job failed with DeadlineExceeded — the run hit
ingest.runDeadlineSeconds (24 hours by default). Either it wedged, or you have
asked for more history than fits in one run; see
Limits for the sizing. Nothing is lost
either way — the walk records each completed day and the next firing resumes
from there. Failing rather than hanging is the point: the CronJob runs one at a
time, so a run that never ends would block every later one indefinitely.
A source says abandoned after …s (ingest.engine.sourceDeadlineSeconds) so the sources behind it could run — it ran for longer than one source may in a run
(ingest.engine.sourceDeadlineSeconds, two hours by default),
so the run stopped it and went on to the next source. When the job's day was
nearly spent the brackets say (what was left of ingest.runDeadlineSeconds)
instead: less than the limit was left. The self test shows it as
a recorded failure until a run completes. What the rest of the sentence says
tells you whether to act:
it resumes where it stopped— a mirror that moved forward. It is catching up on history a little each run. Leave it, or raise the value so it catches up in one.it made no progress, so its next run goes last with the rest of the day's budget— a snapshot or file (the stream it was copying keeps its previous copy; a stream it had finished keeps its new one), or a mirror whose resume point did not move. Its next run gets what is left of that run's time, once (or again, if what was left was less than the limit).stopped … with the day's whole remaining budget … does not fit in ingest.runDeadlineSeconds— even that was not enough, and it moved nothing forward (a mirror that did is toldit resumes where it stopped). It is held to the limit on later runs, which then saydid not fit in ingest.runDeadlineSeconds on an earlier run. The sentence names what the stream reads from: check that statement's plan with your DBA, or the source's rate limits. Another failure or a restart does not end the hold; it ends when a run completes, or when you change the source's manifest, its parameters (on the source or the chart values it reads them from, such asingest.github.orgs) or its uploaded file, eitheringest.engine.sourceDeadlineSecondsoringest.runDeadlineSecondsororacle.mode, or upgrade. Its next run then gets the rest of the day again, once.
Every GitHub ingest run starts the backfill again, and the table never
fills — each Job ends in DeadlineExceeded, each one logs first live run: truncating …, and the row count returns to roughly the same number. Progress is
recorded per completed day bucket, so a run killed before it finishes even
one records nothing — and a run that recorded nothing is indistinguishable from
a first run, which truncates and starts over. It takes a run deadline too short
for one bucket at the configured request rate: a bucket costs at least 3
requests per org and up to 30 on a busy day, so at maxRequestsPerMinute: 1 it
needs anywhere from half a minute to half an hour. Raise
ingest.github.maxRequestsPerMinute, or raise ingest.runDeadlineSeconds back
towards its 24h default — never the other way. Current builds say so in the log
before it happens: a WARNING naming both numbers where a busy bucket will not
fit, and a refusal to start at all (refusing to start, also naming both) where
even an empty one cannot. An ingest that exits immediately with that message is
this check, not a failure to reach GitHub.
The Jira ingest can loop the same way and says so the same way, in its own unit:
it records progress per page of search results, and a page costs one request
whatever it holds, so the whole floor is 3 requests for the run — deployment
detection, the field registry, one page. Where that does not fit inside the
deadline it refuses to start, naming JIRA_MAX_REQUESTS_PER_MINUTE and the
deadline; raise ingest.jira.maxRequestsPerMinute or the deadline.
A source shows rows but upstream unverified — the data is there and the
ingest is current, but nothing has confirmed the upstream is still reachable
with a working credential. That is a real gap rather than a pedantic one: a
token revoked five minutes ago leaves a row count identical to a healthy one,
and the mirror only starts to look wrong days later.
The line names the probe that answers it. Either run it directly:
kubectl create job -n <namespace> probe-github \
--from=cronjob/<release>-github-check
…or work out why the CronJob that does this every ingest.checkSchedule has
not:
kubectl get cronjob,job -n <namespace> -l app.kubernetes.io/component=github-check
The usual causes are a source flipped live by editing a value without running
helm upgrade (so the CronJob was never created), a missing credential secret,
or an image the namespace cannot pull. The probe exits 0 whatever it finds, so a
failed Job here is the datapond, not the upstream — an upstream fault comes
back as a red upstream check failed: <fault> on the self test instead.
Two source lines never ask for a probe, and neither is a fault: directory
has no prism-ingest job at all (the Agent Router is its upstream, and the
AGENT ROUTER checks on the same page cover it), and a live spend source has
no local index to probe.
A source reports no_token or no_credential, and you are certain the
credential is in the Secret — then it is almost certainly under a different
key than the one the chart reads. These are the three, and they are exact:
| Source | Secret key |
|---|---|
| GitHub | github-token |
| GitHub, when the credential is an App | github-app-private-key |
| Jira | jira-token |
Router spend and directory | valet-api-key |
This only arises with existingSecret, where you build the Secret yourself —
a chart-managed Secret always writes the right names. Check for a typo, an
underscore for a hyphen, or a key you added to a different Secret than the one
existingSecret names:
kubectl get secret <your secret> -n <namespace> -o jsonpath='{.data}' | tr ',' '\n'
The verdict in the diagnostic bundle names the key it looked for, so compare that against the output above rather than against your notes. Nothing is broken while this is showing — the credential is simply not being found, and the source keeps serving whatever it has.
helm upgrade came back clean but the source is still empty — expected for
the first half-hour or more after a flip. helm returns once the backfill has
started; the walk itself takes tens of minutes and scales with
ingest.backfillDays and how many orgs or projects you named. Watch it with
kubectl logs -n <namespace> -l app.kubernetes.io/component=github-ingest -f
(or jira-ingest) — it prints a line per day of history. Still empty once that
Job has finished is the credential/egress case above, not a slow backfill.
A source is still not refreshing after the upgrade that was meant to fix
it — a source whose last run failed is held off for an hour before it is
tried again, and until 0.13.0 that hour was measured from the failure alone. It
therefore survived the helm upgrade you performed because it fixes that
source, and for up to an hour afterwards nothing changed. The tick's own log
said so — the last attempt failed 0:23:44 ago; retry after 1:00:00 — but that
line is in a CronJob pod's log rather than on the admin page.
From 0.13.0 there are two ways through, and you should not need either very often:
- Ask for it.
POST /api/admin/sources/<id>/ingest/run-nowrecords a request that the next tick honours — withiningest.engine.tickMinutes(15 by default). There is no button for it on Admin → Context streams; it needs a super-administrator session, the same as the page, and the source'sidis underregistry.rowsinGET /api/admin/sources. It overrides the retry backoff and the schedule, and it asks for one walk however many times you send it. The source's record (GET /api/admin/sources/<id>) says whether one is waiting:run_request_pendingistrueuntil a walk honours it. (run_requested_atis when it was asked for, and stays set after the walk has run, so readrun_request_pending, not that.) To take a waiting request back, sendDELETE /api/admin/sources/<id>/ingest/run-now— the/ingest/run-nowpath, never the source's own:DELETE /api/admin/sources/<id>deletes the source and its stored credential. A request is never discarded on your behalf, because nothing having run is not the same as your having changed your mind. - It is usually unnecessary after an upgrade. An attempt is now stamped
with the version that made it, so a failed source whose appliance has been
upgraded since is tried at the next tick rather than waiting out the hour.
This needs your chart version to have moved: an appliance running an
unversioned chart (
0.0.0) has nothing to compare and keeps the old behaviour. - From 0.16.0, a values-only redeploy counts too, for the values that decide
how a source connects. Correcting
oracle.modeand runninghelm upgradeon the same chart version leaves the version where it was, so until 0.16.0 the failed source still waited out the hour. An attempt is now also stamped with a digest of those values, and a change ends the backoff at the next tick. Today the only such value isoracle.mode. For any other change, the run-now request above is the lever, and from 0.16.0 the tick's not-due line names it.
The tick says a run failed and gives no reason — from 0.16.0 the line says
so in words: "no reason was recorded for it, which usually means the run was
killed rather than failing". Both of the tick's failure paths write a reason
to the source's row, so an attempt with none after it usually means neither ran
to completion. The likeliest cause is that the process was killed: an
out-of-memory kill, or a crash inside a native library. Less often, the write
itself failed, which the tick's own done line reports under red. Look at
the tick pod's exit reason, which the pod log does not carry:
kubectl get pods -n <namespace> -l app.kubernetes.io/component=ingest-tick
kubectl describe pod <the failed pod> -n <namespace> # "Last State: Terminated", "Reason: OOMKilled"
A SIGTERM (the run passing ingest.runDeadlineSeconds, an eviction or a node
drain) is caught from 0.16.0, from the moment the tick starts. If it arrives
while a source is running, that source's reason says "the run was stopped by
SIGTERM before it finished" instead, and no further source is started. If it
arrives before any source starts, the tick only logs terminated and exits 143.
When the deadline is the cause, the Job controller deletes the pod, so
kubectl describe pod finds nothing. The recorded reason and
kubectl describe job are where to look.
On an older release, the escape hatch is to clear the attempt stamp by hand. Clear the one key — not the row:
UPDATE ingest_state SET detail = detail - 'last_attempt_at'
WHERE source = '<the source name>';
DELETE FROM ingest_state WHERE source = … also works and costs more than it
looks: that row holds the resume point of any interrupted backfill, the
backfill horizon and the record described next, so deleting it can send the
next run back to the start of history and truncate what is there. Remove the
key, not the row.
A source that has never finished a run keeps what it has copied, and will not
start again from empty. The first live run of a source clears its tables —
whatever was in them (sample data, an older configuration) must not mix with
real rows — and records that it has done so, as live_since on that source's
ingest_state row. Every later run reads that record: a source that cannot
finish adds to what its own earlier runs copied instead of deleting it every
hour. That is what stops one stream that always fails from wiping the streams
beside it.
You will meet it in the ingest log as a line beginning "a previous run of this source already truncated …", on a source whose Last completed is still empty. It is not an error; it is the run declining to destroy its own earlier work.
If you genuinely want the next run to start from empty — you have changed what the source means, not merely how it is filtered, and the rows already copied are wrong rather than incomplete — remove that one key and let the schedule do the rest:
UPDATE ingest_state SET detail = detail - 'live_since'
WHERE source = '<the source name>';
The next run then clears the tables and backfills, exactly as a first run does. Editing the source's manifest does this for you: a run whose document differs from the one the tables were taken over under takes them over again, because the rows were written by a different document.
Two ingest Jobs for one source, one of them Completed in seconds — not a
fault. Only one run of a source may hold the datapond at a time; a second
(usually the CronJob firing during a first backfill) logs another … ingest is already running and exits successfully rather than racing it.
Review latencies look impossibly fast, or a code-review bot appears as a
person — a time to first review measured in minutes is usually an automated
reviewer being counted as a colleague. Prism classifies a review as automated
when GitHub reports its author as an app; releases before this one classified by
account name instead, which missed every review app whose login does not end in
[bot] (GitHub's own review bots among them). Upgrading fixes what is ingested
from then on, and does not correct history: the flag is written when a review
is stored. To restate the existing rows, clear the GitHub ingest bookkeeping and
let the next run re-walk the history —
DELETE FROM ingest_state WHERE source = 'github';
— then wait for the next CronJob firing (or trigger one). That re-walk is a
first run: it clears the table before it starts, exactly as the warning
further up this page says, and the page said the opposite here until 0.14.0.
Unless INGEST_TRUNCATE_ON_FIRST_RUN=0 is set, expect the table to be empty
while it runs. It takes as long as the original
backfill. Until it has run, review-latency figures and the reviewer matrix may
still count review apps as people. Answers name the accounts they excluded as
automated, so an empty exclusion list beside a two-minute median is the symptom
to look for.
Every question fails instantly, and the self test's chat round trip says "no
answer from the agent" — if the diagnostic bundle's runner_error for that
check mentions toolConfig.tools, the Agent Router in front of this install is
mistranslating the prompt-caching hints Prism attaches to its tool definitions.
A Bedrock-backed gateway folds the cache point into the same toolConfig.tools[]
element as the tool itself, and Bedrock refuses the pair:
400 The value at toolConfig.tools.11 can only set one of the following keys:
toolSpec, systemTool, modelTool, or cachePoint.
Nothing else goes red — the model catalogue, the inference endpoint and every MCP
line stay green, because the request only becomes invalid at the last hop. Set
agentRunner.promptCaching=false and upgrade; the chat works again and the
install gives up the caching discount until the Router is fixed. Report it: the
translation is the Router's to correct, and the setting is meant to be temporary.
If you have set it before and this came back after an upgrade, check the
bundle's config block: prompt_caching_configured says which way the setting
went in on the release that is actually running. It reports the configured
value, including anything you overrode in agentRunner.extraEnv, so on there
means nobody turned it off in your values file — and the usual cause is a values
file that was not passed to the upgrade. Two things it cannot see: a bare
kubectl set env on the runner Deployment (move it into agentRunner.extraEnv,
where it is both visible and durable), and the runner switching caching off by
itself after a gateway rejects the hint, which it does from 0.9.0 and logs.
Everything works, and inference costs far more than expected — Prism re-sends a large, stable prefix on every turn (the instructions and the tool definitions), and asks the Router to cache it. Whether that hint takes effect depends on which Router API the request goes down, and the two do not agree:
| Your Agent Router fronts | agentRunner.anthropicApi |
|---|---|
| AWS Bedrock, or Vertex / AWS Anthropic | completions (the default) |
| the Anthropic API directly | messages |
Set the wrong one and nothing looks wrong. Every check on /selftest stays
green, every answer is correct, and the only symptom is the bill — the cached
part of each turn is worth roughly 9x, so on a long conversation this is most
of what you pay. Confirm before changing it: in the Agent Router's own request
log, a working install shows cache-read tokens on the second and later turns of a
conversation; an install on the wrong path shows none, ever.
This is a Router-side gap rather than a Prism setting we would rather not have —
the gateway carries the caching hint on every path where it translates between
API shapes, and one path passes the request through untranslated instead. When
that is fixed the default will be right everywhere and this parameter goes away.
agentRunner.promptCaching must also be on (it is by default) for either path to
cache at all. The bundle's config block reports both as they were configured —
anthropic_api_configured and prompt_caching_configured — which tells you what
this install was asked to do, but not whether the Router honoured it. Only the
Router's request log answers that. invalid there means the value set is not one
the runner recognises, so it was ignored and the default is in force.
The chat starts answering, then dies part-way — the idle timeout on something between the browser and Prism, not Prism. An answer that makes several tool calls runs for minutes with quiet gaps between events, and Prism sends no keepalive traffic to fill them. Raise the idle timeout on your ingress, gateway and any proxy in the path to at least 10 minutes (Limits). The answer itself says this happened — an amber band above it, carried into the exported PDF — and a cut arriving at about the same elapsed time every time is the timeout rather than bad luck. Nothing in the self test fires for it, and nothing server-side records it: every component is healthy, the answer was produced normally, and the stored copy is complete. What the reader saw is the only evidence, which is why the band exists.
Prism refuses a chart, or says it cannot answer something it clearly has the data for — check Limits before raising it. A chart with spend on one axis and delivery output on the other, a "productivity" heading over a count, and inferring who is on a team are refused by design, not by fault.
Every question on a stream's Verification tab says the stream is not loaded,
while the stream itself is enabled and answering — the questions record which
stream they ask about, and on a stream registered or renamed before 0.12.0 that
record did not move when the name did. So each question asks about a name this
installation no longer has, and reports that there is no such stream to ask.
Nothing else is wrong: the stream is collecting data and answering in chat, and
none of its figures are affected. Open its Manifest tab, set every
query.source under policy.verification.questions to the stream's own name,
and Save. Saving a manifest clears any verdicts already recorded — the questions
come back as pending, which is the ordinary consequence of replacing a model,
not of this fix. From 0.12.0 a rename carries the questions with it and a
manifest whose questions name another stream is refused with the question named.
The chat quotes numbers that look wrong — check the SOURCES group. A source
still in synthetic mode is answering from generated data, which is realistic
enough to look real. This is the intended day-one state, but it surprises people
who expected a flipped source to have taken effect.
A verification question says the proxy in front of Prism gave up
On Admin → Context streams › Verification, a question's card reads "No
result: the proxy in front of Prism gave up on this request after about N s
(HTTP 504)". A fast 502 or 503 reads differently — "could not reach
Prism" — because that is a pod restarting or a service with no ready endpoint
rather than a timeout, and raising a timeout would not touch it. The question was still running when a Route, ingress or load
balancer between you and Prism stopped waiting for it. Nothing was recorded,
and it says nothing about the source. On OpenShift the Route's default timeout
is 30 seconds, and a question over a large copied source can take longer. Raise
the timeout as in Installing Prism, step 1e — in
app.ingress.annotations where the chart renders your Ingress, or a hand
annotation is lost at the next helm upgrade — then press Run on that
question again. Prism's own ceiling on one question is 45 seconds, so past that
the card carries Prism's reason rather than a proxy's, and no Route timeout
raises it. The same N on every attempt is the proxy's timeout;
since 0.16.0 Run all questions sends one request per question, so only a
question that on its own takes longer than the proxy allows is affected.
A source loads nothing, writes no error, and its job is still running
A context stream that copies your Oracle or Postgres starts, loads nothing,
and its ingest job stays Running. The tick walks its sources one after
another, and a new tick does not start while one is still running. Before
0.16.0 the tick therefore ran no other source while it waited. From 0.16.0 one
source runs for at most
ingest.engine.sourceDeadlineSeconds (two hours by default),
or, after it was stopped without moving forward (for a snapshot or file:
without completing), for the rest of the day's ingest. That happens once, and
only when at least the limit was left; if that run is stopped again without
moving forward, the source is held to the limit until a run completes or its
settings or version change. Then it is stopped with an abandoned after … or stopped after … line and the sources behind it run.
From 0.16.0: wait for the bound, then read the line
From 0.16.0 most stalls end on their own with a line that says what stopped them. A line that names one stream can come while the run goes on to the next stream. Read the lines in the tick's log, newest Job last:
kubectl -n <namespace> get jobs --sort-by=.metadata.creationTimestamp \
-l app.kubernetes.io/instance=<release>,app.kubernetes.io/component=ingest-tick
kubectl -n <namespace> logs job/<the job>
The log is kept only until the next tick finishes, or not at all once the
Job has hit its deadline. The tick's failed line
for the source, and last_failure.error in the
diagnostic bundle's ingest block,
carry that line, or a summary that counts the streams refused
(N streams refused (…)).
While a stream is being copied, the log also has a line for it every minute,
<stream>: still copying: N rows in M chunk(s) so far; Ts …, ending with what
the copy has been doing for those seconds: opening a session on the source,
waiting on the source, writing to Prism's database, or one of Prism's own
pauses (pausing between requests to the source (pacing),
backing off after a failed attempt). Rows that keep climbing mean a
slow copy; seconds that keep climbing on the same end mean that end is where
it waits.
How long to wait for the stream the copy is on:
- Setting up a Postgres session: 60 seconds for each of connect, session setup and begin, tried six times: about nine minutes.
- The statement: the manifest's
statement_timeout, or without oneingest.engine.databaseFetchTimeoutSeconds(an hour by default) for each step. On Oracle, every round trip after connecting gets that bound (half the manifest'sstatement_timeout, if it states one), and a stalled fetch can take about twice as long to end: about two hours by default. - Connecting to Oracle: no bound. Each session, once set up, writes
oracle session opened in … modeto the tick's log. The copy connects again after adatabase error (…)line orWARNING: that fault closed the session …. If no neworacle session openedline has come for several minutes after one of those, or since the run began, it may be stuck connecting: go to No such line.
| The line says | What it means |
|---|---|
no answer to connect within 60s | Postgres: the server, or something between it and Prism, did not answer the connection |
no answer to session setup within 60s | Postgres: the connection opened, and a session setting got no answer |
no answer to begin within 60s | Postgres: starting the read-only transaction got no answer |
giving up on <stream> after 6 attempts (the last: …) | Every attempt failed; the part in brackets is the last one's reason or code |
the statement did not finish inside the configured statement_timeout | The manifest states a statement_timeout, and the statement was stopped by it. On Postgres a cancel by your DBA reads the same; on Oracle so does a connection closed late in a statement (DPY-4011) |
… inside the engine's own …s bound on one step of the statement … | The manifest sets no statement_timeout, and one step of the statement returned nothing for the fetch bound. On Oracle a connection closed late in a statement (DPY-4011) reads the same |
… inside a statement_timeout the source's role or cluster carries … | Postgres: 57014 with no statement_timeout in the manifest: a timeout set on the login or the cluster, or a cancel by your DBA |
… inside an Oracle call timeout, or a connection closed before the session was set up … | Oracle, before the session was set up: DPY-4011 is both |
… inside the Oracle call timeout while the session was being set up … | Oracle: a session setting did not finish inside the call timeout (DPY-4024) |
the connection dropped (…) after N page(s) … | The connection closed after that stream had read pages. That stream keeps its previous copy for this run, and the streams after it are copied on a new connection |
abandoned after …s (ingest.engine.sourceDeadlineSeconds) so the sources behind it could run (or its progress could not be read) | None of the bounds above ended it inside the per-source limit, so the tick stopped this source and ran the next; see A source says abandoned after … below |
stopped after … with the day's whole remaining budget … | The same, on the one run a stalled source is given the rest of the day: the copy does not fit in a day, and it is held to the limit until a run completes or its settings or version change; see the same entry |
the run was stopped by SIGTERM before it finished … | No bound fired: the tick's deadline (ingest.runDeadlineSeconds), an eviction, a node drain, or someone deleting the Job stopped it |
For a Postgres source, If something is wrong on Copy a Postgres table or view has entries for several of these. A statement waiting on a lock on your server also ends with one of these lines. To see it, run the query on your server below while a run of the source is waiting on it. For an Oracle source, send us the line with the diagnostic bundle.
No such line, or before 0.16.0
Connecting to Oracle, a wait inside Prism's own Postgres, and a far end that
keeps sending a trickle of data (on Oracle, on Postgres with no
statement_timeout, or a copy that pages) have no bound of their own. From
0.16.0 the per-source limit still stops them, with the abandoned after …
line; before 0.16.0 a copy with no statement_timeout could wait until the
job's deadline. If no
line has come after the wait above, then, while the job is still running,
run these two queries and send us both outputs with the diagnostic bundle. No
rows for the source's login on your server is itself an answer: no session is
open.
On the Postgres Prism uses for its own memory. With the bundled Postgres:
kubectl exec -n <namespace> statefulset/<release>-postgres -- \
psql -U postgres -d prism -c "
select pid, client_addr, backend_start, state, wait_event_type,
wait_event, xact_start, pg_blocking_pids(pid) as blocked_by,
left(query, 60)
from pg_stat_activity where datname = current_database()
order by xact_start nulls last;"
With postgres.bundled: false, run the same select from any client that
can reach your server, against postgres.external.database, as
postgres.external.user.
While the copy waits on your server, its own session here is normally
idle in transaction.
On your server, filtering on the login the source connects as (Parameters
on the source's page shows it as db_user in the recipes). On Postgres, as a
superuser or a role with pg_read_all_stats:
select pid, usename, datname, state, wait_event_type, wait_event,
xact_start, now() - state_change as in_state_for,
now() - query_start as age, left(query, 60)
from pg_stat_activity
where usename = '<the login>'
order by age desc nulls last;
On Oracle, as an account that can read gv$session:
select inst_id, sid, status, event, last_call_et, sql_id
from gv$session where username = upper('<the login>')
order by last_call_et desc;
To free the tick sooner, disable the source, save the Job's log, then delete the Job; its log goes with it. Find it through its running pod, since finished tick Jobs stay listed too:
kubectl -n <namespace> get pods \
-l app.kubernetes.io/instance=<release>,app.kubernetes.io/component=ingest-tick \
--field-selector=status.phase=Running -L job-name
kubectl -n <namespace> logs job/<the JOB-NAME it shows> > tick.log
kubectl -n <namespace> delete job <the JOB-NAME it shows>
When to send it to us
A screenshot of the self test page covers most of it. If we need more, the diagnostic bundle carries the evidence behind every line — see self-test.md for exactly what is and isn't in it.
Worth including if you have it: which step you were on, and whether it ever worked.