name: investigate-workflow-failures description: >- Diagnose why a Discount Analyst dashboard workflow_run_id failed or was cancelled. Groups FAILED and CANCELLED ticker/agent errors from SQLite, Logfire, code, and optional conversations or vendor probes. Distinguishes persist/tool/schema crashes from intended gate or Sentinel rejections. Use when the user runs /investigate-workflow-failures <uuid>, asks why a workflow failed, what broke a UUID, or to triage dashboard pipeline errors. Do not use for qualitative agent-conversation review (see analyse-workflow-run). Do not implement fixes.
Investigate workflow failures
Diagnose causes of every currently FAILED and CANCELLED ticker lane (and a failed Surveyor) for one workflow_runs.id. Do not fix anything.
Intended COMPLETED outcomes (data_quality_rejection, sentinel_rejection, rating-table SELL) are not errors. Mention their count so the batch is not mistaken for a total washout; do not investigate them as failures.
Hard rules
- Diagnose only. Do not patch code, open a fix PR, POST
/retry_failed_agents, cancel, or delete. - Do not destroy data. Host dashboard SQLite is read-only (
mode=roor a copy). Artefacts are new files only. - Redact credentials (
api_token=,apikey=, similar query secrets) in chat and indiagnosis.md. Ticker text and conversations are not confidential; keys are. - Never trust one field.
workflow_runs.error_messageis often null. API detail omits per-runerror_message. Stored SQLite text is the last attempt after retries. Terminal scrollback is the last crash. - Do not copy SQL from
analyse-workflow-run. That skill’s canned queries are stale (finished_at,backend/db/models.py,"exit_code": 0JSON). PRAGMA first; columns frombackend/src/discount_analyst/adapters/persistence/models.py.
When not to use this skill
User asked to review / analyse / audit agent quality, conversations, or Appraiser valuations → analyse-workflow-run.
After this diagnosis, you may point at that skill. Do not run its six-subagent HTML path unless the user asks.
Artefacts
.cursor/artefacts/investigate-workflow-failures/<workflow-run-id>/<n>/
<n> is 1, 2, … — the next unused positive integer under that UUID — so a later investigation does not overwrite an earlier one.
| Path | Purpose |
| ----------------------- | ---------------------------------------------------------------------- |
| diagnosis.md | Required causal report (same content as the chat answer, written down) |
| dashboard.sqlite | Optional copy of the host DB used |
| conversation_digests/ | Only if you exported conversations for unexplained lanes |
.cursor/artefacts/ is gitignored.
Create the folder:
uuid="<workflow-run-id>"
base=".cursor/artefacts/investigate-workflow-failures/${uuid}"
mkdir -p "$base"
next=1
while [ -d "$base/$next" ]; do next=$((next + 1)); done
mkdir -p "$base/$next"
Steps
Copy this checklist and work it in order.
- [ ] 1. Parse UUID; create artefact dir <uuid>/<n>/
- [ ] 2. Check prior artefacts/handoffs for this UUID
- [ ] 3. Locate SQLite (prod → dev → default); abort if 0 rows
- [ ] 4. Optional copy; PRAGMA; run cluster script
- [ ] 5. Logfire exception timeline over the run window
- [ ] 6. For each error bucket: map to code (started_at vs HEAD)
- [ ] 7. Extra evidence only where still unexplained
- [ ] 8. Chat + diagnosis.md; do not fix
- [ ] 9. Update this skill if you learned something durable
1. Identity and prior work
Parse the UUID from /investigate-workflow-failures <uuid> or from the user message.
Before rediscovering: .cursor/artefacts/investigate-workflow-failures/<uuid>/, .cursor/artefacts/analyse-workflow-run/<uuid>/, .cursor/artefacts/handoffs/ (search the UUID).
2. Find the row (stop if missing)
Query in order; stop and ask which DB if all return 0 rows:
| Path | When |
| ---------------------------- | --------------------------------------------------------------------------------- |
| data/dashboard.prod.sqlite | VS Code PROD stack (DASHBOARD_DATABASE_PATH) — default for saved dashboard runs |
| data/dashboard.dev.sqlite | DEV stack |
| data/dashboard.sqlite | Config default when env unset; usually not where prod runs live |
SQLite often stores enum names (FAILED, SURVEYOR, SENTINEL_REJECTION); models.py values and the API JSON are lowercase (failed, surveyor). Always compare with lower(...). WHERE status = 'failed' misses FAILED rows.
Optional: GET http://127.0.0.1:8000/api/workflow_runs/<uuid> for the nested tree. It is not sufficient for error text.
If the API may still be writing, copy then query the copy:
cp data/dashboard.prod.sqlite \
".cursor/artefacts/investigate-workflow-failures/<uuid>/<n>/dashboard.sqlite"
Open copies and host files read-only.
3. Cluster (mandatory, before transcripts)
uv run python .cursor/skills/investigate-workflow-failures/scripts/cluster_workflow_failures.py \
--workflow-id "<uuid>" \
--sqlite-path "<sqlite used in step 2>"
In-scope rows match Retry failed and cancelled agents:
runs.statusin{failed, cancelled}- failed Surveyor (
agent_executions.workflow_run_idset,run_idnull, status failed) - lane executions status failed or cancelled
lane_aborted+ run failed (gate-abort with SKIPPED children)
SKIPPED executions often copy the originating error. The originating FAILED/CANCELLED row is the one with started_at set (conversation may still be missing if the crash was post-agent or mid-stream).
Group before diving. Several unrelated buckets in one workflow is normal.
4. Logfire (fill gaps and recover first cause)
Call Logfire query_schema_reference once. Queries: investigation-queries.md.
- Window from
workflow_runs.started_at→completed_at(pad ~1 hour). Ifcompleted_atis null, use now. Max 14 days; split or warn if the span is longer. - Filter
attributes->>'workflow_run_id' = '<uuid>'. AlwaysLIMIT. - Prefer
is_exception/exception_type/exception_message/span_name/attributes->>'ticker'. attributes->>'agent_name'is often null on pipeline-failed spans. Span names likeSurveyor entry pipeline failedwrap lane failures, not Surveyor-agent guilt.- List exceptions in time order, not just counts. Retries (
Workflow failed-agent retry scheduled, multipleWorkflow execution started) mean SQLite holds the last error. - A week of retries will fill
LIMIT 80with early-batch exceptions. Always also filterattributes->>'ticker'for each in-scope ticker, and pagestart_timestamp > last_seenuntil the window is exhausted. - Project is often
discount-analyst. Generate a UI link for the user. - Sparse tagging: absence of a span does not prove the stage did not run — SQLite executions are occupancy.
5. Map buckets to code
For each cluster, grep the models and adapters that raise that string, then check whether HEAD already diverges from started_at (git log / blame). State “as of the run” vs “as of now”.
Starting pointers (verify; do not freeze August 2026 behaviour):
| Fingerprint | Read |
| --------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| DataQualityRejection vs SentinelRejection model_type | adapters/persistence/crud/run_executions.py (persist_ticker_run_final_verdict), domain/decisions/schema.py |
| FMP 402 / no confident match / resolution_notes | adapters/market_data/candidate_gates.py, fmp_client.py |
| EodhdRealTimeQuote / close='NA' | adapters/market_data/eodhd_client.py |
| run_stream() / EvaluationReport / UnexpectedModelBehavior output validation | agents/runtime/streaming_retries.py, agents/sentinel/schema.py |
| web_fetch max retries | pydantic-ai tool retries (default 1); HTTP status is in the same message or prior span |
| Rate limit reached / TPM | Provider quota; may be the last overlay after earlier gate/persist errors |
| Failed-agent retry overlay | entrypoints/api/routers/workflow_runs.py, prepare_retry_failed_agents |
| Status rollup | adapters/persistence/crud/workflow_runs.py recompute_workflow_status |
Taxonomy: failure-kinds.md.
6. Extra evidence (only if still unexplained)
Allowed and encouraged when SQLite + Logfire do not explain an in-scope lane:
- Conversation digest export (reuse analyse-workflow-run scripts;
--output-dir= this numbered artefact folder). Point--sqlite-pathat the artefact copy, not the live host DB — the exporter opens SQLite read-write. XOR-join Surveyor or you drop it. agent_conversation_message_parts:part_kind = 'tool_return',tool_namein (web_fetch,terminal_exec). Terminal bodies are textexit_code: 0, not JSON"exit_code": 0.%timeout%matchestimeout 600in commands — preferexit_code: 124or Logfire Timeout types.- Live FMP/EODHD GET probes when a vendor-plan or identity cause is suspected. Do not write vendor data into the dashboard DB.
Do not spawn six qualitative subagents to answer a persist ValidationError.
7. Answer
Lead with the answer in chat, then write the same into diagnosis.md.
# Workflow <uuid> — failure diagnosis
- Artefact: `.cursor/artefacts/investigate-workflow-failures/<uuid>/<n>/`
- SQLite: <path> (copied: yes/no)
- Logfire window: <start> → <end>
- Workflow status / `error_message`: …
## In scope
N FAILED + C CANCELLED ticker lanes (of M total). Surveyor: …
## Not errors
K COMPLETED lanes (decision_type breakdown). Do not treat these as failures.
## Buckets
### Bucket 1 — <name> (count, tickers)
- Stored error (redacted):
- Likely root cause (first vs last if retried):
- Evidence (SQLite / Logfire / snapshot / conversation / vendor):
- Code to read:
## What completed
Tickers and `decision_type` (list, no deep dive).
## Unexplained
Lanes still open after all evidence, if any.
Offer analyse-workflow-run only if the user wants qualitative review next.
Maintain this skill
After the investigation, update this skill (SKILL.md, references/, or scripts/) when either is true:
- You found a durable insight future agents should know (new failure fingerprint, join gotcha, Logfire trap, schema change, secret-in-error pattern).
- The skill has drifted from the repo (wrong path, column, enum, stale taxonomy, SQL that fails
PRAGMA).
Keep changes mechanical: fix the classifier or reference, do not freeze one-off ticker stories. Re-read models.py rather than patching memory. Do not “fix” this skill by copying queries from analyse-workflow-run.
If you update the skill, say so in the chat answer in one line.
Architecture agents must hold
Surveyor (workflow-scoped) and/or Profiler (per portfolio ticker)
→ candidate gate (dashboard only)
→ Researcher → Strategist → Sentinel
→ Appraiser if Sentinel proceeds
→ rating table → Verdict
agent_executions parent is XOR. Joining only through runs drops Surveyor.
Profiler/Surveyor can COMPLETE as conversations and the lane still FAILED if the candidate gate or persist after the agent throws.
Expert Next.js App Router
Developpement
Un skill qui transforme Claude en expert Next.js App Router.
Générateur de README
Developpement
Crée des README.md professionnels et complets pour vos projets.
Rédacteur de Documentation API
Developpement
Génère de la documentation API complète au format OpenAPI/Swagger.