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.
Next.js App Router Expert
Development
A skill that turns Claude into a Next.js App Router expert.
README Generator
Development
Creates professional and comprehensive README.md files for your projects.
API Documentation Writer
Development
Generates comprehensive API documentation in OpenAPI/Swagger format.