Skip to main content

troubleshoot

Runs the end-to-end multi-agent diagnostic sequence for an ML training or inference workload on Cloud TPU/GPU (Pathways, MaxText, JAX, Megascale) using public tools and data sources (Cloud Logging, GKE cluster events, Kubelet/Node logs) by orchestrating the wiki_router, wiki_practitioner, wiki_advisor, and wiki_reviewer subagents against the ML Workload Debugging Wiki. Produces a root cause, a culprit-host attribution quoted from logs, and a failure table. Use when the user types "/troubleshoot", says "troubleshoot", "diagnose", "investigate" or "root cause" together with a workload or jobset id, or asks why a workload crashed, hung, restarted or was preempted using public telemetry. Don't use for launching new workloads or configuring dashboards (use the dashboard skill).

Source facts

Repository
google/ml-workload-debugging-wiki
Last source activity
October 25, 2026 at 18:22
Detected SKILL.md language
English
Stars
20
Forks
2

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
troubleshoot
description
Runs the end-to-end multi-agent diagnostic sequence for an ML training or inference workload on Cloud TPU/GPU (Pathways, MaxText, JAX, Megascale) using public tools and data sources (Cloud Logging, GKE cluster events, Kubelet/Node logs) by orchestrating the wiki_router, wiki_practitioner, wiki_advisor, and wiki_reviewer subagents against the ML Workload Debugging Wiki. Produces a root cause, a culprit-host attribution quoted from logs, and a failure table. Use when the user types "/troubleshoot", says "troubleshoot", "diagnose", "investigate" or "root cause" together with a workload or jobset id, or asks why a workload crashed, hung, restarted or was preempted using public telemetry. Don't use for launching new workloads or configuring dashboards (use the dashboard skill).
# `/troubleshoot` — Multi-Agent Workload Diagnosis Antigravity counterpart of the Claude Code `/troubleshoot` command (`.claude/commands/troubleshoot.md`). Both drive the same wiki, the same Actions, and the same invariants; only the harness differs. **Usage:** `/troubleshoot <workload_id>` — or any equivalent phrasing. `<workload_id>` is the jobset/workload name, e.g. `my-tpu-workload`. > [!IMPORTANT] **MUST read first:** `_wiki/playbooks/rca-invariants.md` (access > preflight -> `NO_DATA`, earliest error across all pods, barriers are victims, > workload scoping, verdict outcomes). It overrides any step below; > `_wiki/playbooks/debugging-mindset.md` is optional background. ## Public Surface & Scope Boundary This skill is strictly public-facing: - Operates only on public telemetry sources: Cloud Logging, Kubernetes/GKE cluster events, Kubelet/Node logs, and public Cloud TPU APIs. - **MUST NOT** execute or recommend any action, tool, or workflow marked "internal-only". If a symptom or workflow has `internal_steps` or references internal actions, this skill skips them and uses only public alternatives (e.g., `lookup-workload-timestamps`, `isolate-node-via-rr-label` via `node-isolation-and-rr-api`). ## Harness Notes (read first) - **Subagents** are Antigravity profiles under `_agents/agents/`, invoked with `invoke_subagent`: `wiki_router`, `wiki_practitioner`, `wiki_advisor`, `wiki_reviewer`. - **Commands** run through `run_command`. Start one persistent terminal (`RunPersistent: true`) and reuse its `TerminalID` for every subsequent command. New non-persistent shells bypass corporate credential configuration and fail with Context-Aware Access errors. - **`_agents/scripts/boundary_gate.py`** is registered as a `PreToolUse` hook on `view_file`, `list_dir`, `write_to_file`, `replace_file_content`, `multi_replace_file_content` and `run_command`. If it blocks something, re-scope the call into the wiki rather than working around it. - **Scratch files** go under `_wiki/scratch/` only. ## Action Execution Modes Read `execution_mode` on every Action before running it. The three modes dispatch differently and an Action is not necessarily a shell command. `execution_mode` | Naming | Dispatch in Antigravity :--------------- | :--------------- | :---------------------- `command` | any | `run_command`: `python3 _wiki/tools/scripts/python_glue_runner.py <action> '<params_json>'` `ai` | **`ai-` prefix** | `invoke_subagent` with the Action's interpolated `prompt` `manual` | any | Surface the `instruction` to the user; do not automate For an `ai-` Action: read its `prompt` verbatim, interpolate every declared parameter (both `${name}` and `<name>` styles), then dispatch via `invoke_subagent`, honouring the Action's `model`, `batch_size` and `max_concurrent_workers` hints. Never route an `ai-` Action through the glue runner, and never hand-roll an equivalent analysis in place of the prompt — if it cannot be dispatched, say so. ## Investigation Invariants **These apply to every run, whichever workflow is selected.** They are derived from real misdiagnoses; the canonical list with worked examples is `_wiki/playbooks/workload-disruption-orchestration.md`. 1. **Never infer a culprit host from device coordinates.** `DATA_LOSS: Device device(N,...,slice=K) is unplaced` shows where the symptom *surfaced*; that slice is frequently not the slice that died. Pathways names the real host literally — `lost connection to peer at http://machine/<node>`. Quote it: run `extract-culprit-hosts`. 2. **Never assert a fleet-wide negative over an unverified node set.** Run `resolve-workload-nodes` and confirm `status: COMPLETE` before any "no hardware event" conclusion. 3. **Start every causal chain at the first process death**, not the first reported error. Client-side `DATA_LOSS` / `PlacementDataLoss` / `ValueError` / `JaxRuntimeError` are almost always downstream. 4. **Read `si_code` on every signal.** `0` = `SI_USER` = sent via `kill()` by another process, not a CPU fault. `-6` with `from PID <self>` = self-`abort()` teardown, not an independent fault. 5. **Preemption wording is not preemption evidence.** `EXIT_TYPE_QUICKLY_DURING_PREEMPTION` and `"Resource manager is being preempted"` are emitted for any SIGTERM. Corroborate with an audit event on this workload's own nodes. **Kubelet eviction also sends SIGTERM** — before writing `VM termination` or `preempted`, check `reason=Evicted`, `ephemeral-storage` or `DiskPressure`. A missing exit code alongside SIGTERM wording points at eviction, not at an unknown failure. See `_wiki/symptoms/false-preemption-sigterm.md` and `_wiki/symptoms/missing-exit-codes-eviction.md`. 6. **Restart timelines are drafts.** `job-disruption-timeline` both over-counts (duplicate records at identical timestamps; a clean exit logged as a restart) and under-counts (it cannot see disruptions the workload recovered from). Reconcile against the crash sweep. 7. **Recovered disruptions are findings.** A crash the workload survived proves the fault is survivable and reclassifies failed recoveries as recovery-path defects. 8. **Never conclude from truncated output.** Anything read through `head`/`tail`, or any query where `original_count == limit`, must be re-verified unbounded. 9. **Never infer platform identity.** Accelerator type, topology, chips per host and cluster must be quoted from log entries for this workload timestamped inside the investigated window. Current cluster state (`kubectl`, node pools, `resolve-cluster-metadata`) is not evidence. Never take them from names, `INDEX.md`, or case studies. 10. **Report only what ran — never invent Action IDs.** Every entry in the `"Actions Executed"` table MUST match an existing `_wiki/actions/<id>.json` file whose `python_glue_runner.py` or `ai-` subagent call actually completed. Never invent hyphenated action names (e.g. `diagnose-step-progression`, `check-worker-skew`, `check-inspection-session`) for ad-hoc queries or scratch scripts; record ad-hoc queries separately with their exact command. Do not write or execute custom `.py` scripts under `_wiki/scratch/`. Copy log quotes from tool output, never retype them. 11. **Dynamic Attempt Discovery.** When an operator requests the latest N restart attempts or when investigating multi-attempt disruptions, NEVER assume the restart attempt count is static or bounded by an initial/arbitrary window. Dynamically query `labels."k8s-pod/jobset_sigs_k8s_io/restart-attempt"` across the unclipped workload lifespan (or inspect JobSet status) to discover all distinct attempt indices [0..M], and select the latest N attempts in reverse chronological order. Discover the true unclipped workload lifetime boundary (`first_occurrence` to `last_occurrence`) before time-slicing disruption windows. ## Fast-Path CLI Cheat-Sheet (No Action JSON File Inspection Needed) `wiki_practitioner` MUST invoke actions directly via `python_glue_runner.py` without wasting turns viewing `_wiki/actions/*.json` schemas first: ```bash # 1. Cloud Logging 2-Second Preflight Probe (trips circuit breaker on exit 77 ACCESS_DENIED) python3 _wiki/tools/scripts/python_glue_runner.py validate-workload \ '{"workload": "<workload>", "project_id": "<project_id>"}' # 2. Single Workload-Wide Timestamp & Attempt Discovery python3 _wiki/tools/scripts/python_glue_runner.py lookup-workload-timestamps \ '{"jobset_name": "<workload>", "project_id": "<project_id>", "output_file": "scratch/timestamps_<workload>.json"}' # 3. Full-Window Crash Signatures & Disruption Windows python3 _wiki/tools/scripts/python_glue_runner.py query-crash-signatures \ '{"workload": "<workload>", "project_id": "<project_id>", "start": "<start_utc>", "end": "<end_utc>"}' python3 _wiki/tools/scripts/python_glue_runner.py locate-disruption-windows \ '{"workload": "<workload>", "project_id": "<project_id>", "start": "<start_utc>", "end": "<end_utc>"}' # 4. Parallel Per-Restart-Attempt Deep-Dive (executes all K attempts concurrently via ThreadPoolExecutor) python3 _wiki/tools/scripts/python_glue_runner.py find-earliest-failure \ '{"workload": "<workload>", "project_id": "<project_id>"}' --attempts-json=@scratch/timestamps_<workload>.json python3 _wiki/tools/scripts/python_glue_runner.py extract-culprit-hosts \ '{"workload": "<workload>", "project_id": "<project_id>"}' --attempts-json=@scratch/timestamps_<workload>.json ``` ## Milestone 1: Reconnaissance & Parallel Dispatch - **Multi-Workload Turn-1 Parallel Fan-Out**: When the user asks to investigate $N > 1$ workloads (or multiple independent incidents), the coordinator MUST spawn $N$ `wiki_practitioner` subagents **in parallel in Turn 1** alongside `wiki_router` — one `wiki_practitioner` per workload — so all $N$ workloads execute their Milestone 1 & 2 diagnostics concurrently rather than waiting for `wiki_router` to finish reading files first. Even for a single workload (`N = 1`), spawn `wiki_router` and `wiki_practitioner` concurrently in Turn 1. - **Pre-flight Auth & Access Check (`gcloud-login`)**: Before querying telemetry or cluster logs, verify gcloud authentication and Application Default Credentials (ADC) validity by executing `gcloud-login` with `check_only=true`. If either credential token is invalid or expired, request user approval to run `gcloud-login` to authenticate and refresh credentials. - **2-Second Cloud Logging Preflight Gate, Circuit Breaker (Exit Code 77) & `NO_DATA` Stop Rule**: Run `validate-workload` to establish state (`INITIALIZED` / `FAILED_STARTUP` / `WORKLOAD_NOT_FOUND`) and recover `cluster_name` and `location`. - Diagnostic scripts exit **77** (`ACCESS_DENIED`) or **75** (`QUERY_FAILED`) when Cloud Logging refuses or fails a query. When exit **77** occurs (or log queries fail with `PERMISSION_DENIED`, pending IAM approval `tickets/...`, `logging.logViews.access`, or `invalid_auth`), `python_glue_runner.py` trips the project-scoped circuit breaker (`_wiki/scratch/.cloud_logging_access_denied_cache.json`) and fast-fails any subsequent Cloud Logging actions in `< 50ms`. **STOP immediately** and report verdict `NO_DATA` (`ACCESS_DENIED`) with the exact error; do **not** execute Milestone 2 / 2a actions (`resolve-workload-nodes`, `query-vm-termination-events`, etc.) when log access is blocked, and never record it as "no events" or `WORKLOAD_NOT_FOUND`. - **Phase A — Dynamic Attempt Discovery, Lookback Questionnaire & Lifetime Boundary**: If a specific time range (`start`/`end`) or `lookback_days` is not already specified by the user, present the action's questionnaire to prompt the user how far back to analyze (default: 1 day; options: `1` day (default), `3` days, `7` days, `14` days, `30` days, or custom input). Before time-slicing disruption windows, discover the true unclipped workload lifetime boundary (`first_occurrence` to `last_occurrence`) and all distinct attempt indices `[0..M]` in a single pass using `lookup-workload-timestamps` (passing `--lookback_days=<N>` when specified, or selecting the latest N attempts in reverse chronological order via `--latest_attempts=N` when requested). Note: `lookup-workload-timestamps` queries the coordinator/head pod (`-0-0-`, `-pathways-head`, or `-pwhd`) rather than scanning logs from every worker host across all slices. - Retrieve application and telemetry logs (`query-workload-logs` or `ai-triage-classify-and-mask-logs`). - **Always run `query-crash-signatures` over the FULL run window**, passing explicit `start` and `end`. This is mandatory, not conditional. Without a window the query falls back to gcloud's 1-day default freshness and silently returns nothing for any older incident; and a restart-derived timeline cannot see a disruption the workload recovered from, which is frequently the most informative event in the run. - **Locate disruption windows (`locate-disruption-windows`).** JobSet events are small and fast to query: run it right after the run window is known and use its restart / backoff / teardown `windows` to focus node, culprit, and hardware queries. It does **not** replace the full-window crash sweep above — a disruption the workload recovered from leaves no JobSet restart. - Match error signatures against `_wiki/INDEX.md` and `_wiki/symptoms/`. ## Milestone 2: Direct Workload Diagnostics & Per-Attempt Parallel Fan-Out (wiki_advisor, wiki_practitioner) **Disk-backed State Passing**: The `wiki_practitioner` MUST save diagnostic payloads to `_wiki/scratch/triage-<workload>-<ts>.json` and pass ONLY the summary and artifact file path to `wiki_reviewer` and `wiki_advisor`. Do not pass large log payloads in inter-agent messages. - **Phase B — Parallel Per-Restart-Attempt Investigation**: Once Phase A (`lookup-workload-timestamps` or `locate-disruption-windows`) enumerates the $K$ restart attempts in the target window, **NEVER investigate Attempt $0, 1, \dots, K-1$ in a serial loop**. Pass `--attempts-json=@scratch/timestamps_<workload>.json` (or `"attempts": [...]` in `params`) to `python_glue_runner.py` so per-attempt Cloud Logging queries (`find-earliest-failure`, `extract-culprit-hosts`, `query-vm-termination-events`, `query-node-health-events`, `query-workload-logs`) execute concurrently across all $K$ attempts via `ThreadPoolExecutor`, while preserving cross-attempt correlation (e.g. identifying repeat offender nodes across attempts). - If several symptoms or workflows match, `invoke_subagent` → `wiki_advisor` to resolve the ambiguity and choose the workflow. - `invoke_subagent` → `wiki_practitioner` to interpret the workflow JSON and execute its steps. - Request user approval before any Action with `requires_approval: true`. - Report each Action's `summary_fields` on completion. ### Milestone 2a: Baseline Disruption Evidence (mandatory after any disruption) Run whenever Milestone 1 found readable logs with crash signatures, restarts, or a non-zero exit — **regardless of which symptom-specific workflow was selected**. Skip for `NO_DATA`. For `FAILED_STARTUP`, `WORKLOAD_NOT_FOUND`, pending Kueue workloads (`allows to fit only N out of M slice(s)`), or multi-run JobSet series where some runs never schedule and others crash, route to `workload-run-forensics` and format the output with `run-forensics-report-template`. (Note: Kueue and JobSet are optional on GKE clusters; if the cluster schedules Pods or Jobs directly without Kueue/JobSet CRDs, skip Kueue-specific queue actions and scope log queries by Pod/Job prefix.) 1. `resolve-workload-nodes` — the authoritative node scope. Confirm `status: COMPLETE`; if `INCOMPLETE`, downstream hardware verdicts are inconclusive, not negative. Emits two scoping clauses, which are **not** interchangeable: `node_filter_clause` (`protoPayload.resourceName`) for
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub