vinitv
03/19/2026, 12:14 PM@Marvin I am using a Kubernetes work pool worker where jobs are being created and flows are executing successfully. However, in the Prefect UI, only flow logs are visible, while graphs/charts and task/subflow details are missing.
Is there any required configuration (e.g., environment variables or API settings) that needs to be set before starting the Kubernetes work pool worker?
Marvin
03/19/2026, 12:14 PMMarvin
03/19/2026, 12:17 PM@task functions (or subflows) being called. Prefect only draws the graph and shows task/subflow details when your code uses @task and @flow-decorated callables.
What to check
- Are you decorating the units of work with @task and calling them from a @flow?
- If everything happens inside the flow body (plain Python), you’ll see flow logs but no task/subflow nodes in the UI.
- If you intend to show subflows, make sure the nested function is decorated with @flow and is called from within your parent flow.
- Version consistency: ensure your image used by the job has a recent Prefect 3.x version compatible with your server/Cloud workspace.
Minimal example that will show up in the UI graph
from prefect import flow, task
@task
def do_work(x: int) -> int:
return x * 2
@flow
def parent():
# This creates a task run (shows in graph)
y = do_work(21)
return y
if __name__ == "__main__":
parent()
Kubernetes worker-specific notes
- If your worker is successfully creating jobs and you see flow logs in the UI, the required API settings for orchestration are already in place.
- If you’re using a heavily customized Kubernetes job template, confirm that the job pod still has Prefect’s API environment variables set (the worker injects these by default). You can verify inside a running flow pod:
kubectl exec -it <flow-pod-name> -- env | grep PREFECT_API
You should see PREFECT_API_URL and PREFECT_API_KEY. If the pod can fetch work and send logs, these are usually fine.
If you’d like, paste a small snippet of your flow showing how you’re doing the work, and (if applicable) your custom job template. I can confirm whether tasks/subflows are being created as expected.
Helpful docs
- Tasks concept: Tasks in Prefect 3
- Flows and subflows: Flows and Subflows
- Kubernetes work pools: prefect-kubernetes collectionvinitv
03/19/2026, 12:24 PMMarvin
03/19/2026, 12:24 PMMarvin
03/19/2026, 12:27 PMprefect flow-run execute (which is required for task/subflow tracking).
Most common Kubernetes-only causes of “logs but no graph”
1) Custom job template overrides the entrypoint and bypasses Prefect’s engine
- If your template sets `command`/`args` without including {{ command }}, the pod will not run prefect flow-run execute, so no task/subflow graph is reported.
- Fix: Ensure your container actually executes Prefect’s runner. Either:
- Include the placeholder: args: ["{{ command }}"]
- Or leave args unset and let the worker populate it.
- Also be mindful of container ENTRYPOINTs: if your image has a custom ENTRYPOINT, Kubernetes args become parameters to that ENTRYPOINT. Ensure the final executed command is the Prefect CLI runner, not your app entrypoint.
2) Image/CLI mismatch
- The container image must include a compatible Prefect 3.x installation and the prefect CLI. If prefect flow-run execute isn’t available (or fails early), the observer can still forward logs, but no tasks will be reported.
3) Pod never reaches the runner
- If the pod crashes during startup/imports, tasks won’t be created. The observer still forwards logs.
Quick checks to pinpoint the issue
- Inspect the actual container args/command on a created pod:
kubectl get pod <pod-name> -o jsonpath='{.spec.containers[0].command}{" "}{.spec.containers[0].args}'
You should see something equivalent to prefect flow-run execute .... If not, your template or image ENTRYPOINT is overriding it.
- Look for the runner starting in pod logs:
- You should see lines like “Executing flow run …” from Prefect’s runner. If you never see these, the engine likely didn’t start.
- Verify Prefect is present in the image:
kubectl exec -it <pod-name> -- prefect version
- If you have a custom job template, make sure it includes the Prefect command placeholder:
spec:
template:
spec:
restartPolicy: Never
serviceAccountName: "{{ service_account_name }}"
containers:
- name: flow
image: "{{ image }}"
args: ["{{ command }}"] # <- critical
env: "{{ env }}"
resources: "{{ resources }}"
If your image has a strict ENTRYPOINT, explicitly set command to a shell and pass the placeholder in args, e.g.:
command: ["/bin/sh", "-c"]
args: ["{{ command }}"]
If you share:
- Your work pool’s custom job template (if any)
- The container image/tag you’re using
- A snippet of the first 50 lines of the flow pod logs
…I can confirm exactly where it’s getting bypassed.
Helpful docs
- Kubernetes integration (work pool, templates, observer): prefect-kubernetes docsvinitv
03/19/2026, 12:35 PMMarvin
03/19/2026, 12:35 PMMarvin
03/19/2026, 12:38 PMPREFECT_CLOUD_ENABLE_ORCHESTRATION_TELEMETRY=false is set in the pod env, the flow engine will not emit task/subflow state events. You’ll get logs (via observer) but no task graph.
- Check inside a running flow pod:
kubectl exec -it <pod> -- env | sort | grep PREFECT_
Ensure you do NOT see PREFECT_CLOUD_ENABLE_ORCHESTRATION_TELEMETRY=false.
2) Hitting a different API (Cloud vs self-hosted server) for Kubernetes runs
- If your K8s work pool (or job template) points to a different API URL than your process/Docker pools (e.g., an internal Prefect Server), task recording may be disabled there while it works elsewhere.
- Confirm the pod’s API target matches what your working pools use:
kubectl exec -it <pod> -- env | grep -E "PREFECT_API_URL|PREFECT_API_KEY"
Compare with the env used by the process/Docker pools.
3) Server-side “Task Run Recorder” service disabled (only for self-hosted Prefect Server)
- On Prefect Server (OSS), task visibility depends on the Task Run Recorder service. If it’s disabled, you’ll see flow runs/logs but no tasks.
- On the server, check:
- Env/config flag: PREFECT_SERVER_SERVICES_TASK_RUN_RECORDER_ENABLED should be true
- Or via config: prefect config view | grep task_run_recorder
- This would explain why only runs hitting that server (your K8s pods) lack task graphs.
4) Prefect version mismatch in the image
- If the Kubernetes image has an older or incompatible Prefect version, task-run event emission can break while the flow still runs and logs appear via the observer.
- Check:
kubectl exec -it <pod> -- prefect version
Compare with the version used by your working pools.
Targeted next steps
- From a recent K8s flow pod:
1) Print Prefect env
kubectl exec -it <pod> -- env | sort | grep PREFECT_
Ensure:
- `PREFECT_API_URL`/`PREFECT_API_KEY` are present and point to the same workspace as your working pools
- PREFECT_CLOUD_ENABLE_ORCHESTRATION_TELEMETRY is not set to false
2) Print Prefect version
kubectl exec -it <pod> -- prefect version
3) If you’re on self-hosted Prefect Server for K8s runs, verify the Task Run Recorder service is enabled on that API instance.
If you can share:
- Whether these K8s runs are targeting Prefect Cloud or a self-hosted Prefect Server
- The output of the two commands above (mask secrets)
- Any work pool job template overrides that modify env injection
…I can pinpoint which of the above is in play and help you fix it quickly.
References
- Kubernetes integration (work pools, templates, observer): prefect-kubernetes docs
- Configuration guide: Prefect configurationvinitv
03/19/2026, 12:47 PMtask_run_recorder in the Prefect server is set to enabled: false. However, other types of work pools are still showing graphs and charts in the UI—why is this happening?Marvin
03/19/2026, 12:47 PMMarvin
03/19/2026, 12:50 PMvinitv
03/19/2026, 12:50 PMtask_run_recorder in the Prefect server is set to enabled: false. However, other types of work pools are still showing graphs and charts in the UI—why is this happening?Marvin
03/19/2026, 12:50 PMMarvin
03/19/2026, 12:52 PMtask_run_recorder.enabled=false, so tasks/subflows aren’t persisted—hence no graph—while logs still show because the Event Persister service is enabled and logs are stored as events.
Why this happens
- In Prefect 3.x, task/subflow visibility relies on the server-side Task Run Recorder. If it’s disabled, the UI won’t show task runs or graphs.
- Logs are persisted by the Event Persister service, which can be enabled independently—so logs can appear even when task graphs don’t.
- Process and Docker pools typically inherit your local Prefect Cloud login/config, so they may be sending runs to Cloud (recorder enabled) while your Kubernetes pool is configured (via work pool/env) to send runs to your self-hosted server (recorder disabled).
How to confirm
- Check the API target in each environment:
# Local (process/Docker) environment
prefect config view | grep -E "api\.url|cloud\.api"
env | grep -E "PREFECT_API_URL|PREFECT_API_KEY"
# Inside a Kubernetes flow pod
kubectl exec -it <pod> -- env | grep -E "PREFECT_API_URL|PREFECT_API_KEY"
You’ll likely find process/Docker use Cloud, and the K8s pods use your self-hosted API.
How to fix (pick one)
- Re-enable the Task Run Recorder on your Prefect Server and restart services:
export PREFECT_SERVER_SERVICES_TASK_RUN_RECORDER_ENABLED=true
# or via config file: server.services.task_run_recorder.enabled=true
- Or point the Kubernetes work pool/pods to the same API as your other pools (e.g., Prefect Cloud) via work pool variables/env.
Helpful docs
- Task Run Recorder (what it is and the setting): Server service: Task Run Recorder
- Task Run Recorder service API reference: Task Run Recorder API
- Self-hosted architecture/services: Self-hosted architecturevinitv
03/19/2026, 1:03 PMPREFECT_RESULTS_PERSIST_LOCATION before starting the Kubernetes work pool worker to resolve this issue? I don’t see anything different in the setup.
How is it that the same worker is sending logs to the correct server but the tasks/chart data seem to be going elsewhere? I’ve also verified inside the job pod that the PREFECT_API_URL and PREFECT_API_KEY are correctly configured.Marvin
03/19/2026, 1:03 PMMarvin
03/19/2026, 1:04 PMPREFECT_RESULTS_PERSIST_LOCATION has nothing to do with task/subflow graphs. It only controls where your task/flow return values are stored. Task/subflow visibility in the UI comes from task-run events being recorded by your server’s Task Run Recorder service.
Given your details, here’s what explains the behavior and how to verify it:
Why logs show up but the task graph doesn’t
- Logs can come from the Kubernetes Observer, which forwards pod logs to your server independently of the flow process’ task instrumentation. So “logs visible” does not mean task events were emitted or recorded.
- The task/subflow graph appears only when:
1) The flow process emits task state events (client-side), and
2) The server’s Task Run Recorder service is enabled and consuming those events into TaskRun records.
You mentioned task_run_recorder.enabled: false on your server. If that flag is actually applied to the API instance receiving the events, new task runs will not be materialized — so no graph — even though logs still arrive.
But other pools show graphs — how?
- Most commonly, they’re hitting a different API instance (or replica) than your Kubernetes jobs. In Kubernetes, it’s easy for different callers to resolve/load-balance to different pods with different env/config. One API pod may have the recorder enabled while another has it disabled.
- Another possibility: the setting you saw is not the one applied to the running API process (e.g., you checked a client machine’s config or an old Deployment, not the live API pod).
- Less common: your K8s flow pods may have PREFECT_CLOUD_ENABLE_ORCHESTRATION_TELEMETRY=false, which stops the client from emitting task events at all. In that case you’d see logs (observer) but no tasks, while your other pools (without that flag) still show tasks.
What to check next (targeted)
1) Confirm the setting on the actual API pod(s) that your runs hit
- Check env on each API replica:
kubectl exec -it -n <ns> <prefect-api-pod> -- env | grep PREFECT_SERVER_SERVICES_TASK_RUN_RECORDER_ENABLED
kubectl exec -it -n <ns> <prefect-api-pod> -- env | grep PREFECT_SERVER_SERVICES_EVENT_PERSISTER_ENABLED
Ensure the recorder is enabled (true) on all API pods behind your service.
- If possible, also check the service logs for the recorder starting:
kubectl logs -n <ns> <prefect-api-pod> | grep -i "Task Run Recorder"
2) Verify the flow pod is emitting task events (not disabled by telemetry flag)
- In a recent flow pod from the Kubernetes pool:
kubectl exec -it <flow-pod> -- env | sort | grep PREFECT_
Ensure you do NOT see PREFECT_CLOUD_ENABLE_ORCHESTRATION_TELEMETRY=false.
3) Sanity-check Prefect versions
- Make sure the image used by the Kubernetes jobs has a compatible Prefect 3.x and the prefect CLI:
kubectl exec -it <flow-pod> -- prefect version
4) Ensure all pools truly use the same API URL
- Compare what each environment is using (process/Docker vs K8s):
- Local (process/Docker):
prefect config view | grep -E "api\.url|cloud\.api"
env | grep -E "PREFECT_API_URL|PREFECT_API_KEY"
- Inside a K8s flow pod:
kubectl exec -it <flow-pod> -- env | grep -E "PREFECT_API_URL|PREFECT_API_KEY"
What will actually resolve the issue
- Enable the Task Run Recorder on the server instance(s) your K8s pods hit:
# Server env
PREFECT_SERVER_SERVICES_TASK_RUN_RECORDER_ENABLED=true
# Keep the Event Persister on:
PREFECT_SERVER_SERVICES_EVENT_PERSISTER_ENABLED=true
Then restart the API deployment so the setting applies uniformly to all replicas.
- If you find PREFECT_CLOUD_ENABLE_ORCHESTRATION_TELEMETRY=false set in the K8s job pods, remove it so the client emits task events.
References
- Task Run Recorder setting: Server service: Task Run RecorderMarvin
03/19/2026, 1:04 PMPREFECT_RESULTS_PERSIST_LOCATION for UI graphs. Align the server config so Task Run Recorder is enabled on the API instance(s) your Kubernetes jobs use, and ensure the flow pods aren’t disabling client telemetry. That will restore task/subflow visibility.vinitv
03/20/2026, 7:55 AMMarvin
03/20/2026, 7:55 AMMarvin
03/20/2026, 8:02 AMpip install "prefect[otel]"
- Or install individually (equivalent/minimal):
pip install opentelemetry-distro opentelemetry-exporter-otlp opentelemetry-instrumentation
- Optional (better server traces if you want framework/db spans):
pip install opentelemetry-instrumentation-fastapi opentelemetry-instrumentation-sqlalchemy opentelemetry-instrumentation-logging
Common OTEL environment
Set these before starting any process (server or services):
export OTEL_SERVICE_NAME=prefect-server # or prefect-services when starting services
export OTEL_TRACES_EXPORTER=otlp
# Choose one protocol + endpoint that matches your collector:
# gRPC (default)
export OTEL_EXPORTER_OTLP_ENDPOINT=<http://localhost:4317>
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
# or HTTP
# export OTEL_EXPORTER_OTLP_ENDPOINT=<http://localhost:4318>
# export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
# Optional:
# export OTEL_TRACES_SAMPLER=parentbased_traceidratio
# export OTEL_TRACES_SAMPLER_ARG=0.2
# export OTEL_EXPORTER_OTLP_HEADERS="x-honeycomb-team=<API_KEY>" # if your vendor requires headers
Enable OTEL for the Prefect API server ONLY
- Run the webserver without services and wrap it with the OTEL auto-instrumenter:
opentelemetry-instrument prefect server start --no-services
- Tip: If you want to rename the service to be explicit for the API, set OTEL_SERVICE_NAME=prefect-api.
Enable OTEL for Prefect Server “services” ONLY
- Start just the background services (scheduler, late runs, etc.) in a separate process:
# Optionally control which services run:
# export PREFECT_API_SERVICES_SCHEDULER_ENABLED=true
# export PREFECT_API_SERVICES_LATE_RUNS_ENABLED=true
export OTEL_SERVICE_NAME=prefect-services
opentelemetry-instrument prefect server services start
Run both with OTEL together (one process)
- If you prefer a single process for both webserver + services:
export OTEL_SERVICE_NAME=prefect-server
opentelemetry-instrument prefect server start
Notes and tips
- Framework/db spans: If you install opentelemetry-instrumentation-fastapi and opentelemetry-instrumentation-sqlalchemy, the opentelemetry-instrument wrapper will pick them up and emit richer spans for API routes and DB calls.
- Flow/task spans vs server/services spans:
- Server/services OTEL above is “process auto-instrumentation” (FastAPI/SQLAlchemy/etc).
- Prefect also emits orchestration traces for flow/task runs when enabled. That’s controlled by:
export PREFECT_CLOUD_ENABLE_ORCHESTRATION_TELEMETRY=true # default is true
- If you also want OS-level CPU/memory metrics during flow execution (not the server), enable:
export PREFECT_TELEMETRY_ENABLE_RESOURCE_METRICS=true # default is true
# And point your metrics exporter to OTLP HTTP if needed:
# export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=<http://localhost:4318/v1/metrics>
- Verified CLI flags:
- prefect server start --no-services runs just the API webserver.
- prefect server services start runs the background services.
- Quick Jaeger test collector:
docker run -d -p 4317:4317 -p 4318:4318 -p 16686:16686 jaegertracing/all-in-one:latest
# Then browse <http://localhost:16686> to see traces
Docs and references
- Telemetry concepts: Telemetry concepts
- Telemetry settings API: Telemetry settings (API ref)Marvin
03/20/2026, 8:02 AMvinitv
03/23/2026, 9:54 AMMarvin
03/23/2026, 9:54 AMMarvin
03/23/2026, 9:59 AMMarvin
03/23/2026, 9:59 AMWhat changed vs. the default template
- Added three volumes under spec.template.spec.volumes:
- krb5-config (ConfigMap with krb5.conf)
- krb5-keytab (Secret with your keytab)
- krb5-ccache (EmptyDir to share the credential cache file)
- Added a second container in spec.template.spec.containers (the Kerberos sidecar) that:
- mounts the keytab, krb5.conf, and shared ccache volume
- runs kinit once then periodically renews the ticket
- Mounted krb5.conf and the shared ccache into the prefect-job container and set KRB5CCNAME so your flow can use the ticket
- Kept prefect-job as the first container; Prefect streams logs and runs the flow in this container
How to apply this to your work pool
- Via CLI
prefect work-pool create "my-k8s-pool" --type kubernetes --base-job-template ./kerberos-base-job.json
# or update an existing pool
prefect work-pool update "my-k8s-pool" --base-job-template ./kerberos-base-job.json
```
- Via Prefect Cloud UI: Work Pools -> your Kubernetes pool -> Edit -> Base job template
Notes and gotchas
- The main flow container should remain first and usually named prefect-job
- You can also use an initContainer to perform an initial kinit if you prefer a one-time setup before the flow starts; the sidecar is helpful to keep tickets fresh during long runs
- Prefect will still inject its own env vars and any environment variables you set on the deployment; listing env as an array in the template is fine—your entries will be preserved and Prefect-managed env vars will be merged in
- Make sure your image for both containers includes Kerberos tooling (kinit or k5start), and that the ConfigMap/Secret names match your cluster resources
Docs and references
- Customize base job templates: Customize base job templates
- Kubernetes work pools/workers: Run on Kubernetes (work pools)
- Work pools concept: Work pools (concepts)
- Manage work pools: Manage work pools
If you share the names of your Secrets/ConfigMaps (or whether you want initContainer-only vs. always-on renewal), I can tailor the template exactly to your setup.vinitv
03/23/2026, 10:21 AMenableIdentityHelper in a standard Kubernetes Deployment as shown below, and it correctly injects the init and sidecar containers:
apiVersion: apps/v1
kind: Deployment
metadata:
name: prefect-worker
spec:
selector:
matchLabels:
app: prefect-worker
replicas: 1
template:
metadata:
labels:
app: prefect-worker
enableIdentityHelper: "true"
appIdentity: prefect
spec:
securityContext:
runAsUser: 227
runAsGroup: 228
fsGroup: 99
However, when I add the same labels to the base job configuration template for the Prefect Kubernetes work pool, the created Jobs only include the prefect-job container — no init or sidecar containers are injected.
What might be missing here?
How can I enable similar sidecar/init container injection for jobs created via the Prefect Kubernetes work pool?Marvin
03/23/2026, 10:21 AMMarvin
03/23/2026, 10:23 AM{
"variables": {
"type": "object",
"properties": {
"namespace": {
"type": "string",
"default": "default"
},
"annotations": {
"type": "object",
"default": {}
},
"labels": {
"type": "object",
"default": {}
}
},
"required": [
"namespace"
]
},
"job_configuration": {
"job_manifest": {
"apiVersion": "batch/v1",
"kind": "Job",
"metadata": {
"namespace": "{{ namespace }}",
"generateName": "{{ name }}-",
"labels": "{{ labels }}",
"annotations": "{{ annotations }}"
},
"spec": {
"backoffLimit": "{{ backoff_limit }}",
"ttlSecondsAfterFinished": "{{ finished_job_ttl }}",
"template": {
"metadata": {
"labels": "{{ labels }}",
"annotations": "{{ annotations }}"
},
"spec": {
"parallelism": 1,
"completions": 1,
"restartPolicy": "Never",
"serviceAccountName": "{{ service_account_name }}",
"containers": [
{
"name": "prefect-job",
"image": "{{ image }}",
"imagePullPolicy": "{{ image_pull_policy }}",
"args": "{{ command }}",
"env": "{{ env }}"
}
]
}
}
}
}
}
}
Then set labels/annotations at deploy time or on the pool, for example:
- labels: { "enableIdentityHelper": "true", "appIdentity": "prefect" }
- and any annotations your injector requires
Apply the template to your pool
- CLI:
prefect work-pool update "my-k8s-pool" --base-job-template ./base-job-template.json
- Or edit the pool in the UI and paste the template.
Why this works
- Most sidecar injectors watch AdmissionReview requests for Pod objects created from Job.spec.template. They match on spec.template.metadata.{labels,annotations}. Without that section, your labels stay on the Job and the Pod is unlabelled, so no mutation occurs.
References
- Run on Kubernetes (work pools): Run on Kubernetes (work pools)
- Customize base job templates: Customize base job templates
If you share the exact label/annotation keys your identity helper needs (and whether it requires namespace/selector setup), I can tailor the JSON to your injector’s expectations.vinitv
03/23/2026, 10:28 AMenableIdentityHelper: "true"
appIdentity: prefect
in above job template ? I did not see that.Marvin
03/23/2026, 10:28 AMMarvin
03/23/2026, 10:29 AM{
"job_configuration": {
"job_manifest": {
"apiVersion": "batch/v1",
"kind": "Job",
"metadata": {
"namespace": "{{ namespace }}",
"generateName": "{{ name }}-",
"labels": {
"enableIdentityHelper": "true",
"appIdentity": "prefect"
}
},
"spec": {
"backoffLimit": "{{ backoff_limit }}",
"ttlSecondsAfterFinished": "{{ finished_job_ttl }}",
"template": {
"metadata": {
"labels": {
"enableIdentityHelper": "true",
"appIdentity": "prefect"
}
},
"spec": {
"parallelism": 1,
"completions": 1,
"restartPolicy": "Never",
"serviceAccountName": "{{ service_account_name }}",
"containers": [
{
"name": "prefect-job",
"image": "{{ image }}",
"imagePullPolicy": "{{ image_pull_policy }}",
"args": "{{ command }}",
"env": "{{ env }}"
}
]
}
}
}
}
}
}
If you prefer to keep the template generic and pass labels via variables, keep:
"metadata": { "labels": "{{ labels }}" },
"spec": { "template": { "metadata": { "labels": "{{ labels }}" }, ... } }
and set labels to include:
{
"enableIdentityHelper": "true",
"appIdentity": "prefect"
}
(e.g., as defaults in variables.properties.labels.default, or via work pool/deployment labels).
Key point: spec.template.metadata.labels must include these keys — job-level labels alone won’t trigger the pod-level injector. If your injector uses annotations instead, mirror the same structure under annotations.vinitv
03/23/2026, 1:31 PMapp: <name> label also be included along with the existing labels for the sidecar injection to work?
If yes, what would be the appropriate value for app, considering that Prefect generates a new Job name for each run?Marvin
03/23/2026, 1:31 PMMarvin
03/23/2026, 1:31 PM"metadata": {
"namespace": "{{ namespace }}",
"generateName": "{{ name }}-",
"labels": {
"enableIdentityHelper": "true",
"appIdentity": "prefect",
"app": "prefect" // optional, only if your injector requires it
}
},
"spec": {
"template": {
"metadata": {
"labels": {
"enableIdentityHelper": "true",
"appIdentity": "prefect",
"app": "prefect" // ← required here for pod-level injection
}
},
"spec": {
"containers": [
{
"name": "prefect-job",
"image": "{{ image }}",
"args": "{{ command }}",
"env": "{{ env }}"
}
]
}
}
}
If you’d rather keep the template generic, keep:
- metadata.labels: "{{ labels }}"
- spec.template.metadata.labels: "{{ labels }}"
…and set labels to include app: prefect in your pool/deployment configuration.
Tip:
- Some injectors key off annotations instead of labels. If yours does, mirror the same placement under spec.template.metadata.annotations.
- Prefect generates names for Jobs/Pods, but labels are independent—use a constant label for matching.
Docs:
- Customize base job templates: Customize base job templates
- Run on Kubernetes (work pools): Run on Kubernetes (work pools)vinitv
03/24/2026, 6:28 AMMarvin
03/24/2026, 6:28 AMMarvin
03/24/2026, 6:29 AM"spec": {
"template": {
"metadata": {
"labels": {
"enableIdentityHelper": "true",
"appIdentity": "prefect"
// add "app": "prefect" if your injector matches on it
},
"annotations": {
// add any injector-required annotations here
}
},
"spec": { ... }
}
}
- Or explicitly add a Kerberos sidecar/initContainer and required volumes in the base job template for the pool.
- Do not rely on the worker pod’s environment or sidecars to “carry over” to the Job pod—nothing from the worker pod is automatically shared with Job pods (env, volumes, processes, ticket caches).
If you share which injector you’re using (and whether it matches on labels or annotations), I can provide a ready-to-paste base job template snippet.
References
- Customize base job templates: Customize base job templates
- Run on Kubernetes (work pools): Run on Kubernetes (work pools)vinitv
03/24/2026, 11:24 AMenableIdentityHelper: true is being transformed into enableidentityhelper: true, which is preventing the sidecar from being injected.
I am passing this configuration through the Job settings in the Prefect UI for the Kubernetes work pool.
Could you help clarify why this key normalization is happening and how it can be resolved? I’m currently using Prefect version 3.6.22.Marvin
03/24/2026, 11:24 AMMarvin
03/24/2026, 11:29 AMenableIdentityHelper becomes enableidentityhelper, and your injector doesn’t match.
What’s happening
- Prefect runs all label and annotation keys through a slugifier to keep them Kubernetes‑compliant. This uses python-slugify with the default lowercase behavior.
- Code references:
- Key slugification: prefect_kubernetes/utilities.py (_slugify_label_key)
- Where it’s applied: prefect_kubernetes/worker.py (_slugify_labels, _slugify_annotations, and propagation to pod metadata)
- Note: Label values preserve casing in recent 3.x releases, but keys are still lowercased. You’re on 3.6.22, which has this behavior.
Why this breaks injectors
- Many sidecar injectors match on an exact key (e.g., enableIdentityHelper). Since Prefect lowercases the key to enableidentityhelper, the injector selector doesn’t fire.
Can I bypass this by hardcoding the label in the base job template?
- No. Even labels you place under spec.template.metadata.labels in the base job template are merged and passed back through the slugifier by the worker, so keys will be lowercased there too.
Ways to resolve
- Preferred: Use a lowercase key variant if your injector can accept it (e.g., configure it to look for enableidentityhelper instead of enableIdentityHelper), or use an alternative key it already supports that’s lowercase.
- If your injector supports annotations and accepts lowercase keys there, move the trigger to an annotation. Be aware: Prefect also lowercases annotation keys, so the injector must accept the lowercase key.
- If the injector requires the exact camelCase label/annotation key and cannot be changed:
- Don’t rely on the injector for Jobs. Instead, explicitly add the Kerberos init/sidecar (and any volumes/volumeMounts) in the work pool’s base job template. This avoids key‑matching entirely and is the most reliable path with Prefect today. Docs: Customize base job templates.
- If you want injector-based behavior long-term:
- Consider updating the injector’s webhook to match case-insensitively or to recognize the lowercased key as an alias.
- If this is not feasible, I recommend opening a Prefect feature request to allow opting out of key lowercasing (or a “raw passthrough” for keys). I’m happy to file one on your behalf if you share any constraints/requirements.
If you can share which injector you’re using (and whether it can be configured to match lowercase keys or annotations), I can suggest the cleanest path or craft an explicit base job template snippet that bakes in the Kerberos sidecar/init logic.vinitv
04/08/2026, 12:38 PMvinitv
04/08/2026, 12:40 PMMarvin
04/08/2026, 12:40 PMMarvin
04/08/2026, 12:46 PMpip install "prefect[otel]"
# or at minimum:
pip install opentelemetry-sdk opentelemetry-exporter-otlp-proto-http
2) Turn on Prefect’s built-in span creation and metrics
- These are on by default, but you can set explicitly:
# Traces (spans for flows and tasks)
PREFECT_CLOUD_ENABLE_ORCHESTRATION_TELEMETRY=true
# Resource metrics (CPU / memory for the flow-run process)
PREFECT_TELEMETRY_ENABLE_RESOURCE_METRICS=true
PREFECT_TELEMETRY_RESOURCE_METRICS_INTERVAL_SECONDS=10
3) Point metrics to your OTLP endpoint
- Prefect’s resource metrics exporter follows standard OTel env vars:
# Either a base endpoint…
OTEL_EXPORTER_OTLP_ENDPOINT=<http://otel-collector:4318>
# …or a metrics-specific one
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=<http://otel-collector:4318/v1/metrics>
4) Configure a trace exporter (required for traces to appear in your backend)
By default, Prefect creates spans but does not wire up a trace exporter. Add one at process startup (e.g., in your flow’s entrypoint, a small prestart module, or a sitecustomize.py in your worker image):
# otel_init.py
from opentelemetry import trace
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
import os
endpoint = os.getenv("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT") or \
os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT", "<http://otel-collector:4318>")
resource = Resource.create({
"service.name": os.getenv("OTEL_SERVICE_NAME", "prefect-worker"),
# add useful context for filtering in your backend
"deployment": os.getenv("PREFECT__DEPLOYMENT__NAME", ""),
"work_pool": os.getenv("PREFECT__WORK_POOL__NAME", ""),
})
provider = TracerProvider(resource=resource)
processor = BatchSpanProcessor(OTLPSpanExporter(endpoint=f"{endpoint.rstrip('/')}/v1/traces"))
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)
Import this as early as possible in your worker/flow process, before your flows run:
import otel_init # noqa: F401 # ensure this runs before running flows/tasks
Where to set this up in workers
- Docker/Kubernetes workers: add the env vars to your work pool’s base job template and bake otel_init.py into your image. Import it from your flow entrypoint or via sitecustomize.
- Process/Local workers: set the env vars in the worker environment and ensure otel_init.py is imported before flows execute.
What you’ll see
- Traces:
- Spans per flow run and task run
- Attributes like flow/task names, run IDs, tags; events on state changes; exceptions on failure
- Parent/child relationships across subflows/tasks
- Metrics:
- Process-level CPU and memory for the flow-run subprocess
- Note: metrics are not per-task; they’re for the flow-run process
Notes and limitations
- Resource metrics are collected from the flow-run subprocess, so you’ll see them when running via workers/serve; not when running flows inline in the same process as your Python interpreter.
- If you’re sending to a custom collector (Jaeger, Grafana Tempo, etc.), use the OTLP HTTP endpoint (e.g., http://collector:4318). Prefect won’t attach any Prefect API credentials to custom endpoints.Marvin
04/08/2026, 12:46 PMvinitv
04/08/2026, 2:12 PMMarvin
04/08/2026, 2:12 PMMarvin
04/08/2026, 2:14 PMkubectl get pods -n <namespace> -l job-name=<prefect-generated-job-name> -o wide
# or, if you have the flow run ID:
kubectl get pods -n <namespace> -l "<http://prefect.io/flow-run-id=<FLOW_RUN_ID>|prefect.io/flow-run-id=<FLOW_RUN_ID>>"
- Describe and get full YAML to see termination reason, message, and signal:
kubectl describe pod <pod> -n <namespace>
kubectl get pod <pod> -n <namespace> -o yaml
kubectl get pod <pod> -n <namespace> -o jsonpath='{.status.containerStatuses[0].state.terminated.reason}{" "}{.status.containerStatuses[0].state.terminated.exitCode}{" "}{.status.containerStatuses[0].state.terminated.signal}{"\n"}{.status.containerStatuses[0].state.terminated.message}{"\n"}{.status.containerStatuses[0].lastState.terminated.reason}{"\n"}'
- If your pod used restartPolicy=OnFailure and restarted, check the previous logs:
kubectl logs <pod> -n <namespace> --previous
- Check events for eviction, node pressure, or image/runtime errors:
kubectl get events -n <namespace> --sort-by=.lastTimestamp | grep <pod>
2) Check actual container logs (they may not have reached Prefect)
- If the abort happens before the Prefect engine initializes, Prefect’s own logs can be empty. Always pull raw container logs via kubectl as above.
3) Turn on extra diagnostics in your job
- Enable Prefect debug logs:
PREFECT_LOGGING_LEVEL=DEBUG
- Enable Python faulthandler to dump tracebacks on fatal signals (including SIGABRT) to stderr:
PYTHONFAULTHANDLER=1
- If you suspect native memory errors, enable allocator diagnostics:
PYTHONMALLOC=debug
MALLOC_CHECK_=2
These can surface useful messages right before the abort.
4) Watch for common native-library pitfalls
- ABI/GLIBC mismatches between your image and the node
- CPU instruction set mismatches (less likely for SIGABRT; SIGILL is more common there)
- Mixed binary wheels (e.g., combining different manylinux variants)
- Large native allocations (e.g., Arrow, Torch) that fail abruptly
Do Kubernetes jobs restart automatically?
- By default with a Prefect Kubernetes work pool:
- restartPolicy: Never
- backoffLimit: 0
- Result: no container restarts and no new pod retries by Kubernetes
- You have three retry layers you can choose from:
1) Kubernetes Job retries (new pod on failure)
- Increase backoffLimit in your work pool’s base job config/variables:
{
"spec": {
"backoffLimit": 3
}
}
Kubernetes will create up to 3 new pods if the previous one fails.
2) Pod-internal restarts (same pod restarts the container)
- Customize your base job template to use:
{
"spec": {
"backoffLimit": 0,
"template": {
"spec": {
"restartPolicy": "OnFailure"
}
}
}
}
This restarts the container within the same pod on failure.
3) Prefect-level retries (recommended for idempotent flows/tasks)
- Add retries to your flow and/or tasks so Prefect can reschedule on crash:
from prefect import flow, task
@task(retries=3, retry_delay_seconds=30)
def my_task():
...
@flow(retries=2, retry_delay_seconds=60)
def my_flow():
my_task()
Notes on eviction/reschedulingMarvin
04/08/2026, 2:14 PMvinitv
05/08/2026, 4:54 AMMarvin
05/08/2026, 4:55 AMMarvin
05/08/2026, 4:58 AMredis-cli.
Where to look
- Redis set key: dlq
- Individual messages: keys like dlq:<uuid>
Useful commands
bash
# how many failed messages are in the DLQ
redis-cli SCARD dlq
# list message keys
redis-cli SMEMBERS dlq
# inspect one message
redis-cli HGET dlq:<message_id> data
If you want to inspect all of them:
bash
redis-cli SMEMBERS dlq | while read msg_id; do
echo "=== $msg_id ==="
redis-cli HGET "$msg_id" data
done
If jq is available:
bash
redis-cli SMEMBERS dlq | while read msg_id; do
echo "=== $msg_id ==="
redis-cli HGET "$msg_id" data | jq .
done
A few important details
- This DLQ is part of the Redis messaging layer used in HA setups.
- Messages are sent there after retry attempts are exhausted.
- The Prefect event persister itself does not have a separate DLQ in the API DB; if persistence keeps failing beyond its retry threshold, those events are typically only reflected in logs.
What to verify in your setup
For HA Redis-backed events, you should be using Redis messaging settings like:
- PREFECT_MESSAGING_BROKER="prefect_redis.messaging"
- PREFECT_MESSAGING_CACHE="prefect_redis.messaging"
If you’re not using the Redis broker and are on the default in-memory broker, the DLQ is instead on disk under:
bash
~/.prefect/dlq
So in your case:
- HA + Redis configured correctly → inspect Redis key dlq
- non-Redis/default broker → inspect ~/.prefect/dlq
If you want, I can also help you with:
- a kubectl command to inspect this in your Prefect server pod
- a small script to replay/export DLQ messages
- how to confirm from config whether your API is actually using prefect_redis.messagingvinitv
05/08/2026, 5:03 AMMarvin
05/08/2026, 5:03 AMMarvin
05/08/2026, 5:04 AMbash
PREFECT_MESSAGING_BROKER=prefect_redis.messaging
PREFECT_MESSAGING_CACHE=prefect_redis.messaging
PREFECT_SERVER_EVENTS_CAUSAL_ORDERING=prefect_redis.ordering
You can check this in a few ways.
If running in Kubernetes
bash
kubectl get deploy -n <namespace>
kubectl describe deploy <prefect-server-deployment> -n <namespace>
Then look for env vars in the output:
- PREFECT_MESSAGING_BROKER
- PREFECT_MESSAGING_CACHE
- PREFECT_SERVER_EVENTS_CAUSAL_ORDERING
A more direct version:
bash
kubectl get deploy <prefect-server-deployment> -n <namespace> -o yaml | grep -A2 -E 'PREFECT_MESSAGING_BROKER|PREFECT_MESSAGING_CACHE|PREFECT_SERVER_EVENTS_CAUSAL_ORDERING'
If your deployment has multiple containers:
bash
kubectl get pod -n <namespace>
kubectl describe pod <pod-name> -n <namespace>
If running with Docker
bash
docker inspect <container-name> | grep -A2 -E 'PREFECT_MESSAGING_BROKER|PREFECT_MESSAGING_CACHE|PREFECT_SERVER_EVENTS_CAUSAL_ORDERING'
From inside a running container/pod
bash
env | grep PREFECT_MESSAGING
env | grep PREFECT_SERVER_EVENTS_CAUSAL_ORDERING
Interpretation
- If you see prefect_redis.messaging → you’re using Redis-backed messaging, so inspect Redis DLQ.
- If not, you may still be on the default memory broker, so inspect ~/.prefect/dlq.
---
2) Redis DLQ inspection for HA setup
If you’re using Redis messaging, the dead-letter queue is in Redis.
Check whether there are failed messages
bash
redis-cli SCARD dlq
If that returns 0, there are currently no dead-lettered messages.
List all failed message keys
bash
redis-cli SMEMBERS dlq
You’ll see items like:
bash
dlq:abc123...
dlq:def456...
Inspect one message
bash
redis-cli HGET dlq:<message_id> data
Example:
bash
redis-cli HGET dlq:abc123 data
Pretty-print with jq
bash
redis-cli HGET dlq:abc123 data | jq .
Inspect all messages
bash
redis-cli SMEMBERS dlq | while read msg_id; do
echo "=== $msg_id ==="
redis-cli HGET "$msg_id" data
done
With `jq`:
bash
redis-cli SMEMBERS dlq | while read msg_id; do
echo "=== $msg_id ==="
redis-cli HGET "$msg_id" data | jq .
done
Count and summarize quickly
bash
echo "DLQ count: $(redis-cli SCARD dlq)"
---
3) Redis DLQ inspection from Kubernetes
If Redis is running as a pod/service in your cluster, you can exec into the Redis pod.
Find Redis pod
bash
kubectl get pods -n <namespace>
Exec into Redis pod
bash
kubectl exec -it <redis-pod> -n <namespace> -- redis-cli SCARD dlq
List DLQ keys
bash
kubectl exec -it <redis-pod> -n <namespace> -- redis-cli SMEMBERS dlq
Inspect a message
bash
kubectl exec -it <redis-pod> -n <namespace> -- redis-cli HGET dlq:<message_id> data
If your Redis requires host/port instead of local socket:
bash
kubectl exec -it <prefect-api-pod> -n <namespace> -- sh
redis-cli -h <redis-service-name> -p 6379 SCARD dlq
redis-cli -h <redis-service-name> -p 6379 SMEMBERS dlq
If Redis requires auth:
bash
redis-cli -h <redis-host> -p 6379 -a '<password>' SCARD dlq
---
4) Export Redis DLQ messages to a file
This is useful if you want to inspect them safely before deleting anything.
bash
redis-cli SMEMBERS dlq | while read msg_id; do
redis-cli HGET "$msg_id" data
done > prefect_dlq_dump.jsonl
If you want message ids alongside payloads:
```bashMarvin
05/08/2026, 5:04 AMIf the payload is already JSON, this can work nicely for analysis.
---
*5) Remove / clean Redis DLQ messages*
Only do this after you’ve exported or reviewed them.
*Delete one message*
```bash
redis-cli DEL dlq:<message_id>
redis-cli SREM dlq dlq:<message_id>
Delete all DLQ message entries
bash
redis-cli SMEMBERS dlq | xargs redis-cli DEL
redis-cli DEL dlq
If xargs is not available or message count is small:
bash
for key in $(redis-cli SMEMBERS dlq); do
redis-cli DEL "$key"
done
redis-cli DEL dlq
---
6) Filesystem DLQ inspection for non-Redis/default broker
If your server is not using Redis messaging, failed messages go to a local DLQ directory under Prefect home.
Usually:
bash
~/.prefect/dlq
Sometimes in containers this may resolve under the container user’s home, so check:
bash
echo $HOME
ls -lah ~/.prefect
ls -lah ~/.prefect/dlq
List messages
bash
ls -lah ~/.prefect/dlq
Inspect one file
bash
cat ~/.prefect/dlq/<message_file>
Pretty-print one file
bash
jq . ~/.prefect/dlq/<message_file>
Inspect all files
bash
for f in ~/.prefect/dlq/*; do
echo "=== $f ==="
cat "$f"
done
With formatting:
bash
for f in ~/.prefect/dlq/*; do
echo "=== $f ==="
jq . "$f"
done
Count messages
bash
ls -1 ~/.prefect/dlq | wc -l
Archive them before cleanup
bash
mkdir -p ~/.prefect/dlq_backup
cp ~/.prefect/dlq/* ~/.prefect/dlq_backup/
Delete one file
bash
rm ~/.prefect/dlq/<message_file>
Delete all files
bash
rm -f ~/.prefect/dlq/*
---
7) Filesystem DLQ inspection in Kubernetes
If you suspect the API pod is using the memory broker, exec into the Prefect pod:
bash
kubectl exec -it <prefect-api-pod> -n <namespace> -- sh
Then:
bash
echo $HOME
ls -lah ~/.prefect
ls -lah ~/.prefect/dlq
If the container runs under a different home path, search:
bash
find / -type d -name dlq 2>/dev/null
Or:
bash
find / -path '*/.prefect/dlq' 2>/dev/null
---
8) Check Prefect logs for dropped or dead-lettered events
There are two important failure modes:
Message broker / consumer failures
These may lead to DLQ entries.
Search logs for:
bash
kubectl logs <prefect-api-pod> -n <namespace> | grep -i "dead letter"
kubectl logs <prefect-api-pod> -n <namespace> | grep -i "sending to dead letter queue"
kubectl logs <prefect-api-pod> -n <namespace> | grep -i "could not be processed after"
Event persister failures
These do not necessarily create DLQ entries. Events can be dropped after retries.
Search for:
bash
kubectl logs <prefect-api-pod> -n <namespace> | grep -i "dropping"
kubectl logs <prefect-api-pod> -n <namespace> | grep -i "max flush retries"
kubectl logs <prefect-api-pod> -n <namespace> | grep -i "event queue"
If using Docker:
bash
docker logs <prefect-container> 2>&1 | grep -i "dead letter"
docker logs <prefect-container> 2>&1 | grep -i "max flush retries"
docker logs <prefect-container> 2>&1 | grep -i "dropping"
---
9) Quick health-check flow I’d use in HA
If I were troubleshooting this in your HA self-hosted setup, I’d do:
bash
# 1. confirm Prefect is configured to use Redis messaging
kubectl get deploy <prefect-server-deployment> -n <namespace> -o yaml | grep -A2 -E 'PREFECT_MESSAGING_BROKER|PREFECT_MESSAGING_CACHE|PREFECT_SERVER_EVENTS_CAUSAL_ORDERING'
# 2. check whether DLQ has messages
kubectl exec -it <redis-pod> -n <namespace> -- redis-cli SCARD dlq
# 3. if > 0, inspect them
kubectl exec -it <redis-pod> -n <namespace> -- redis-cli SMEMBERS dlq
# 4. inspect Prefect API logs for why processing failed
kubectl logs <prefect-api-pod> -n <namespace> | grep -iE 'dead letter|dropping|max flush retries|redis'
---
10) Small script: inspect Redis DLQ nicely
```bash
#!/bin/shMarvin
05/08/2026, 5:04 AM---
*11) Small script: inspect filesystem DLQ nicely*
```bash
#!/bin/sh
DLQ_PATH="${HOME}/.prefect/dlq"
if [ ! -d "$DLQ_PATH" ]; then
echo "DLQ path not found: $DLQ_PATH"
exit 1
fi
echo "DLQ files count: $(ls -1 "$DLQ_PATH" 2>/dev/null | wc -l)"
echo
for f in "$DLQ_PATH"/*; do
[ -e "$f" ] || continue
echo "=== $f ==="
jq . "$f" 2>/dev/null || cat "$f"
echo
done
---
12) Important note on replaying messages
I don’t see a built-in supported Prefect command for “replay DLQ messages” directly. In practice, operators usually:
- inspect/export the failed messages
- identify the root cause
- clear the DLQ
- let fresh events flow normally
If you need replay behavior, that would usually require a custom script and careful validation of payload format.
---
Relevant references
- Prefect docs: Prefect documentation
- Prefect repo: PrefectHQ/prefect
- Prefect Redis integration package lives in the Prefect repo and provides prefect_redis.messaging
If you want, I can next give you a copy-paste exact checklist for Kubernetes HA with placeholders like:
- <namespace>
- <prefect-api-deployment>
- <redis-pod>
so you can run it step by step in your cluster.vinitv
05/08/2026, 5:12 AMMarvin
05/08/2026, 5:12 AMMarvin
05/08/2026, 5:12 AMSCARD dlq and SMEMBERS dlq return nothing, the most likely possibilities are:
1. There are currently no DLQ messages
2. You are connected to the wrong Redis instance / DB
3. Prefect is not actually using Redis messaging
4. The failure is happening later in the pipeline, so events are being dropped in logs rather than sent to the Redis DLQ
Let’s narrow it down.
First: what exactly do you see?
Please share the raw output of these:
bash
redis-cli SCARD dlq
redis-cli SMEMBERS dlq
redis-cli INFO keyspace
Because:
- SCARD dlq returning 0 means the key exists but is empty
- (nil) or blank can mean the key does not exist
- INFO keyspace helps confirm which Redis DB has keys
---
Check that you are on the correct Redis DB
Sometimes Prefect is using Redis DB 1 or another DB, while redis-cli defaults to DB 0.
Try:
bash
redis-cli -n 0 SCARD dlq
redis-cli -n 1 SCARD dlq
redis-cli -n 2 SCARD dlq
redis-cli -n 3 SCARD dlq
Also:
bash
redis-cli -n 0 DBSIZE
redis-cli -n 1 DBSIZE
redis-cli -n 2 DBSIZE
redis-cli -n 3 DBSIZE
If one DB has keys and the others don’t, inspect that DB further.
You can also search for likely Prefect keys:
bash
redis-cli --scan | grep -E 'dlq|event|prefect'
Or for a specific DB:
bash
redis-cli -n 0 --scan | grep -E 'dlq|event|prefect'
---
Check whether Prefect is really configured to use Redis messaging
If Prefect is not using prefect_redis.messaging, then the Redis DLQ will stay empty forever.
From the Prefect API/server pod/container:
bash
env | grep PREFECT_MESSAGING
env | grep PREFECT_SERVER_EVENTS_CAUSAL_ORDERING
You want to see:
bash
PREFECT_MESSAGING_BROKER=prefect_redis.messaging
PREFECT_MESSAGING_CACHE=prefect_redis.messaging
PREFECT_SERVER_EVENTS_CAUSAL_ORDERING=prefect_redis.ordering
If you do not see those, then you’re probably not using the Redis event broker.
In Kubernetes:
bash
kubectl exec -it <prefect-api-pod> -n <namespace> -- env | grep PREFECT_MESSAGING
kubectl exec -it <prefect-api-pod> -n <namespace> -- env | grep PREFECT_SERVER_EVENTS_CAUSAL_ORDERING
---
If not using Redis messaging, check the filesystem DLQ instead
Exec into the API pod and inspect:
bash
kubectl exec -it <prefect-api-pod> -n <namespace> -- sh
ls -lah ~/.prefect/dlq
If needed:
bash
find / -path '*/.prefect/dlq' 2>/dev/null
---
Check if the issue is event dropping, not DLQ
A very important detail: some failures do not end up in the Redis DLQ. If the event persister fails repeatedly, Prefect may drop events and only log that.
Search API/server logs:
bash
kubectl logs <prefect-api-pod> -n <namespace> | grep -iE 'dead letter|dropping|max flush retries|redis|event'
Especially look for:
- sending to dead letter queue
- Message could not be processed after
- Max flush retries
- dropping
If you see dropping-related logs, then your missing events may never have reached the Redis DLQ.
---
Check whether Prefect is writing anything at all into Redis
If Prefect is using Redis messaging correctly, you should often see stream/group-related keys too, not just dlq.
Try:
bash
redis-cli --scan | grep -E 'events|stream|consumer|dlq'
And:
bash
redis-cli TYPE dlq
If Redis has no Prefect-related keys at all, either:
- wrong Redis host
- wrong DB
- Prefect not configured for Redis messaging
- Prefect cannot connect to Redis
---
Best next commands
Please run these and paste the output:
bash
redis-cli SCARD dlq
redis-cli SMEMBERS dlq
redis-cli TYPE dlq
redis-cli INFO keyspace
redis-cli --scan | grep -E 'dlq|event|prefect'
And from the Prefect API pod:
bash
env | grep PREFECT_MESSAGING
env | grep PREFECT_SERVER_EVENTS_CAUSAL_ORDERING
If you want, paste those outputs here and I’ll tell you exactly which case you’re in.vinitv
05/14/2026, 12:45 PMMarvin
05/14/2026, 12:45 PMMarvin
05/14/2026, 12:45 PM$ref-based schemas:
* Prefect blocks are not supported as flow run parameters
* [UI v2] Custom run parameter values not preserved
* Prefect Cloud UI parameters missing type hints
Your error:
cannot use 'IN' operator to search for $ref in undefined
looks like a frontend schema/parsing bug in the custom run form, likely triggered by a deployment parameter schema the UI doesn’t fully handle.
A few quick things to check:
* Does it happen only for one deployment, or all deployments?
* Are you using complex parameter types like Pydantic models, nested objects, unions, dataclasses, blocks, or optional structured params?
* Does switching to the raw JSON parameters editor avoid the issue?
If you want, I can help narrow it down and draft a minimal repro. If you’d like to raise a ticket, the best path is:
* Prefect GitHub Issues
If you share:
* your Prefect version
* whether this is Cloud or self-hosted Server
* the flow parameter types / schema
…I can help determine whether this matches one of the known schema-related bugs or help you write a stronger bug report.