Skip to content

lookout bundle

The first call of every incident: one correlated snapshot of a workload — sanitized spec, everything abnormal, broken dependency edges, blast radius, distilled logs — sectioned into a single payload instead of 4–5 separate reads.

MCP tool: k8s_triage_workload (MCP profile: triage)

Terminal window
lookout bundle [flags]
FlagTypeDefaultMeaning
--incidentstring—inject payload JSON (the message a lookout-watch incident session starts with); its object reference resolves to the target workload via the owner chain, or for a Service via its selector — alternative to —workload
--depthint2blast-radius traversal depth: graph edges followed per direction in the radius section
--max-templatesint15cap distilled log template clusters in the logs section (triage logs defaults to 40; the bundle keeps the tighter budget)
--cert-warnduration720hreport TLS certificates expiring within this window (edges section)
--storestring—path to a sentinel’s SQLite store (its —store file); merges open triage-status records so the bundle’s findings carry triage_* fields and severity reflects the agent’s override
--store-clusterstring—read/write the store for THIS cluster, treating —store as the multi-cluster stem the sentinel was given: —store=/var/lib/lookout/lookout.db —store-cluster=prod-us opens /var/lib/lookout/lookout-prod-us.db (issue #410). Set it only against a sentinel running —clusters/—clusters-from; a single-cluster sentinel writes the literal —store path
--listsstringallwhich cluster resources the List pass reads: ‘all’ (default), a comma-separated allowlist (pods,deployments), or subtractions (all,-secrets) for a least-privilege posture. Denied or deselected lists degrade to a partial bundle with a skipped= note on the head, never an error.
--lists-preflightbool—before listing, SelfSubjectAccessReview each selected resource and drop the denied ones proactively (fewer 403s); falls back to reactive Forbidden-skip if SSAR is not permitted
FlagTypeDefaultMeaning
--namespacestring—limit the scan to one namespace
-Abool—scan all namespaces
--workloadstring—target one workload as <Kind>/<namespace>/<name>, e.g. Deployment/prod/api
--sinceduration—how far back to look (0 = command default)
--formatstringlogfmtoutput format: logfmt|json (one record per line either way)
--timeoutduration10sabort the invocation after this long (exit 1)
--kubeconfigstring—path to a kubeconfig file, instead of $KUBECONFIG / ~/.kube/config
--contextstring—kubeconfig context to read, instead of its current-context. Selects a cluster for THIS invocation only — nothing is written back — so concurrent invocations can target different clusters. Reported as context=<name> in the summary line
--exemptionsstring—path to a git-reviewed exemption file (YAML); covered findings are ANNOTATED with their reason and expiry and counted as exempt=<n> in the summary, never dropped

Every kind= this command can emit, and the severities it carries them at. Nothing else appears in its output; a kind absent from a run means the check looked and found nothing. See the finding-kind glossary for the whole vocabulary.

KindSeverityClaim
bundle.targetinfothe head record: which workload the bundle is about and which sections follow
radius.neighborinfoone object in the target’s neighborhood, with its direction, relation, and hop distance — an enumeration of impact, not a defect
radius.missingwarninga neighbor the graph references but never observed, in a kind the snapshot does watch: the reference is dangling
spec.resourceinfothe object itself: metadata, owner, and the kind-specific highlights (one per target)
spec.containerinfoone container of the target: image, resources, ports, probes, env (one per container)
spec.conditionwarninga status condition of the target that is not in its nominal state
pod.crashloopcriticala container is crash looping
pod.imagepullcriticala container cannot pull its image
pod.waitingwarninga container is stuck in an error waiting state (CreateContainerConfigError, InvalidImageName, …)
pod.oomkilledwarninga container’s last termination was an OOM kill
pod.restartswarninga container has restarted at least —restarts times
pod.notreadywarninga container in a Running pod has been not-ready past the —pending-age grace
pod.failedwarningthe pod reached phase Failed
pod.pendingcritical, warningthe pod has been Pending longer than —pending-age with no container-level diagnosis; critical when the scheduler has declared it Unschedulable, which is a capacity or constraint problem rather than latency
workload.replicafailurecriticalthe controller cannot create pods at all (quota, PodSecurity, admission) — no pod exists to diagnose
workload.stalledcriticala Deployment’s Progressing condition is False: the rollout has given up
workload.rolloutcritical, warningreplicas are short of desired; critical when nothing is serving at all
job.failedwarninga Job’s Failed condition is set
cron.missedcritical, warningan unsuspended CronJob’s schedule said to run more than —cron-grace ago and status says it did not; critical once several activations in a row are gone
cron.unparseablewarninga CronJob’s spec.schedule could not be parsed, so its activations cannot be judged at all
node.notreadycriticalthe node’s Ready condition is not True
node.pressurecriticalthe node reports Memory/Disk/PID pressure
node.conditioncritical, warninga non-standard node condition is True — NPD and its cousins publish problems that way
node.cordonedwarningthe node is unschedulable but still holds pods: a stuck drain or a forgotten maintenance step
node.preemptcritical, warning, infoa reclaim taint marks the node for termination; severity tracks how imminent
pdb.gridlockedcritical, warningthe budget permits no disruptions; critical when healthy pods are already below the required minimum
addon.degradedcritical, warninga kube-system add-on (dns, proxy, cni, csi, metrics, connectivity) is short of replicas; critical when none are available
quota.nearwarninga ResourceQuota resource is at or past —quota-warn percent of its hard limit
quota.exhaustedcriticala ResourceQuota resource is at its hard limit: the next create is rejected
log.templatecritical, warning, infoone distilled template and how many lines collapsed into it; severity is the guessed level — critical at fatal, warning for error-ish, info otherwise
log.stacktracecritical, warning, infoa template that is a Go panic, Java exception, or Python traceback, with its innermost frames
log.overflowinfothe low-count tail —max-templates dropped, counted rather than discarded silently (no coverage lies)
log.probe_noiseinfohealth/readiness probe request lines stripped before distillation, counted so the removal is visible
log.fetch_errorwarninga container’s log stream could not be read, so its lines are missing from the distillation
edge.missing_refcriticala referenced ConfigMap, Secret, ServiceAccount, TLS secret, IngressClass, StorageClass, or governing Service does not exist
edge.missing_keycriticalthe referenced key is absent from an existing ConfigMap/Secret
edge.invalid_refwarningthe referenced object exists but is the wrong type to serve the reference
edge.unclassedwarningthe Ingress names no class and no IngressClass declares itself the cluster default — no controller will claim it
edge.selector_emptycriticala Service selector selects zero pods, so the service routes nowhere
edge.selector_unreadycritical, warningthe Service selects pods but some are not Ready; critical when none are
edge.endpoints_missingcriticala selecting Service has no EndpointSlices at all
edge.endpoints_orphanedwarningan endpoint targetRef names a pod that no longer exists
edge.endpoints_unreadycritical, warningthe endpoint ready-count disagrees with the selected pods (stale or lagging slices); critical at zero ready
edge.backend_missingcriticalan Ingress backend service, or the port it names, does not exist
edge.cert_expiredcriticala TLS certificate’s NotAfter is in the past
edge.cert_expiringwarninga TLS certificate expires within —cert-warn
edge.cert_invalidwarningtls.crt is missing or unparseable, or the secret is not kubernetes.io/tls
edge.rbac_danglingwarninga (Cluster)RoleBinding for the workload’s ServiceAccount points at a missing (Cluster)Role

Beyond the shared envelope fields (kind, severity, namespace, kind_of_object, name, reason, message, fingerprint, exempt_reason, exempt_expires):

FieldMeaning
sectionwhich bundle section the finding belongs to: spec|delta|edges|radius|logs
sectionson the bundle.target head finding: the sections that follow
skippedon the bundle.target head finding: comma-separated resources the List pass could not read (denied) or was told to omit (—lists) — the bundle is a documented partial, secret-free by default under a least-privilege role
relationradius neighbor’s relation to the target: upstream (routes/owns/governs it), downstream (it points at), lateral (shares a node/volume/config)
hopradius neighbor’s BFS depth from the target (1 = direct edge)
triage_statustriage state from the matched record (investigating|triaged|actioned|escalated) — present only with —store on merged findings
triage_root_causethe incident agent’s root-cause hypothesis, from the matched triage-status record
triage_actionthe incident agent’s paper trail (PRs opened, escalations), from the matched triage-status record
triage_sessionincident session that wrote the matched triage-status record
triage_agehow long ago the matched triage-status record was last updated
labelsresource labels as sorted k=v pairs
ownercontrolling owner as Kind/name
phasestatus.phase, only when abnormal for the kind (zero nominal state)
nodenode the pod is scheduled on
service_accountpod’s service account
volumespod volumes as name:source (source names its referent, never its payload)
containercontainer name (one spec.container finding per container)
init“true” when the container is an init container
imagecontainer image reference
requestsresource requests as sorted k=v pairs
limitsresource limits as sorted k=v pairs
portscontainer or service ports, compact ([name:]port[->target][/proto])
livenessliveness probe one-liner (kind, target, non-default timings)
readinessreadiness probe one-liner
envenv vars; literal credential values are [REDACTED], valueFrom entries render as named references
env_fromenvFrom sources as kind:name
replicasdesired replica count
strategyrollout strategy summary (type + non-default knobs)
selectorworkload/service selector as sorted k=v pairs
typeService or Secret type, only when non-default
external_nameExternalName service target
session_affinityservice session affinity, only when not None
keysConfigMap/Secret data KEYS with byte sizes — values are never rendered
conditionabnormal status condition as Type=Status
sincethe condition’s lastTransitionTime
speckinds without a dedicated renderer: sanitized spec flattened to path=value pairs
restartscontainer restart count
exit_codeexit code of the container’s last termination
last_statereason of the container’s last termination (e.g. OOMKilled)
agehow long the abnormal state has persisted
desireddesired replica/scheduled count from spec
readyready count from status
updatedupdated-to-current-revision count from status
availableavailable count from status
failedfailed pod count of a Job
schedulea CronJob’s spec.schedule
expectedthe activation a CronJob should have run and did not
missed_runsactivations missed since the anchor; ≥N when the walk was capped
anchorwhat the missed count was measured from: last_schedule or creation
time_zonea CronJob’s spec.timeZone, when set
last_schedulea CronJob’s status.lastScheduleTime, or never
active_jobsJobs a CronJob still has running
tainttaint key indicating reclaim/drain
podspods affected (behind a cordoned node or a PDB)
healthycurrently healthy pods behind a PDB
requiredpods the PDB requires healthy
addonsystem add-on role: dns, proxy, cni, csi, metrics, connectivity
resourceResourceQuota resource name at or near its limit
usedquota usage from status
hardquota hard limit from status
pctquota usage as percent of the hard limit
templatelog template; <*> marks positions that varied across merged lines
countlines merged into this cluster (on log.probe_noise: probe lines stripped)
levelguessed log level (fatal|error|warn|info|debug) from token/field match
first_seenRFC3339 timestamp of the oldest merged line (from log timestamps when parseable)
last_seenRFC3339 timestamp of the newest merged line
langstack-trace runtime on log.stacktrace findings: go|java|python
framestop stack frames on log.stacktrace findings, innermost first, ’ < ’ separated
sampleone representative raw line, truncated and sanitized
omitted_templatesclusters dropped by —max-templates (log.overflow only)
omitted_lineslines inside the dropped clusters (log.overflow only)
workloadthe target the edges were traced from as <Kind>/<namespace>/<name>, stamped on every finding — a workload, or the Service itself when entered from the service side
likely_workloadon a Service-entry edge.selector_empty: the workload in that namespace whose pod labels best fit the broken selector, i.e. the one it was probably meant to select. Absent when two workloads fit equally well, because then naming one would be a guess
volumepod volume, or StatefulSet volumeClaimTemplate, whose reference is broken
keythe referenced key that is missing from the ConfigMap/Secret
selectedpods the Service selector currently selects
endpointstotal endpoints across the Service’s EndpointSlices
sliceshow many EndpointSlices back the Service
servicethe Service a slice, Ingress backend, or StatefulSet serviceName refers to
podpod named by an orphaned endpoint targetRef
subjectTLS certificate subject (CN when set); never key material
not_afterTLS certificate NotAfter, RFC 3339
days_leftwhole days until NotAfter (negative = expired)
viahow the broken reference is reached from the workload: mount, ingress, or imagePullSecret
ingressIngress referencing the TLS secret, or the unserved Ingress itself
hostIngress rule host of the broken backend (empty for the default backend)
pathIngress rule path of the broken backend
portService port (name or number) the Ingress backend asks for
role_refdangling roleRef as <Kind>/<name>

Output: one finding per line (logfmt; —format=json for one JSON object per line), keys in fixed order; healthy resources emit nothing. The final line is always the summary: scanned= findings= elapsed= — findings=0 with a summary present means “scanned and healthy”; a stream without a summary line is void. Exit 0 data, 1 runtime error (diagnostics on stderr only), 2 usage.

Terminal window
lookout bundle --workload=Deployment/prod/api
lookout bundle --workload=StatefulSet/db/postgres --since=30m --format=json
lookout bundle --incident='{"namespace":"prod","kind_of_object":"Pod","name":"api-6d5f8c-x2v9k"}'