scistudio-debug-run
SkillMonitoring & opsUse when a workflow run has failed, is stuck, or produced unexpected output, and you need to diagnose the cause. Covers run-status inspection, block-log retrieval, lineage navigation, common error signatures, and the finish_ai_block contract for AI block PTYs. NOT for designing new workflows (use scistudio-build-workflow).
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the scistudio-debug-run skill
What this skill tells your AI
The instructions your AI receives, as published by jiazhenz026/scistudio in src/scistudio/_skills/scistudio/scistudio-debug-run/SKILL.md and read by ahel’s review.
A SciStudio run has terminated in failed or cancelled state, or you
suspect a block is producing wrong output. This skill teaches the
canonical diagnostic sequence — start at the run-status envelope,
drill into per-block logs, follow lineage backwards to find the
upstream cause, and inspect intermediate data refs without
materialising them into memory. Most run failures fall into ~6
recurring categories; the error catalog below maps each to the next tool call.
1. The canonical diagnostic sequence
get_run_status(run_id) # full envelope
get_block_logs(run_id, failed_block) # stdout/stderr for the failed block
inspect_data(upstream_ref) # confirm input type/shape
get_lineage(failed_input_ref) # walk backwards to producing block
# Form hypothesis; propose fix to user; do NOT silently retry.
Never skip any step. The status envelope alone often understates the failure; the logs surface the real Python traceback; lineage shows upstream contamination.
2. get_run_status envelope shape
GetRunStatusResult(
run_id: str,
state: "queued" | "running" | "succeeded" | "failed" | "cancelled" | "unknown",
progress: {"block_states": {block_id: STATE_NAME, ...}},
errors: [BlockErrorEntry(block_id, error, summary), ...]
)
Read every field. The per-block state map lives at
progress.block_states (not at the top level); captured block-level
errors with full Python tracebacks live in the top-level errors
list. The summary field (when present) is a one-line digest; the
error field is the full traceback. Start by scanning
progress.block_states for the first non-succeeded entry, then
locate its matching BlockErrorEntry in errors.
3. get_block_logs patterns
get_block_logs(run_id, block_id) returns {stdout: str, stderr: str, source: str}. Read both streams and check source, which says which
artifact answered:
codeblock_exchange— a Code Block's per-run script logs. A true stdout/stderr split;stdoutholds whatever the script printed.run_log— the per-run engine log filtered to this block. Everything the block wrote arrives instderr;stdoutis empty by design, because the engine routes block output onto one stream.
Common signatures:
Traceback (most recent call last):— Python exception. Find the bottom-most line; that names the exception and the offending call.FileNotFoundError: [Errno 2] No such file or directory: '...'— block'sconfig.pathpoints to a file that does not exist. Check the config; verify the file exists under the project root.MemoryError— input too large to materialise. Recommend chunking (iter_chunks()inside the block) or a smaller input.KeyError: 'X'insiderun(self, inputs, config)— the block expected an input portXthat was not wired. Check the workflow YAML edges.pydantic.ValidationErrorin block config —config_schemamismatch. Check the workflow YAML's nodeconfigagainst the block's schema (get_block_schema).ImportError— stalereload_blocksor a missing dependency in the block's Python file.subprocess.TimeoutExpired— AI block timed out;config.timeout_secwas too low for the prompt's actual runtime.
When citing a log line to the user, copy the line verbatim. Do not paraphrase tracebacks.
4. get_lineage for cascading failures
If block B failed because its input was malformed, the upstream
block A was the real cause. get_lineage(input_ref) returns the
ancestor chain:
upstream_output = get_block_output(run_id=run_id, block_id=upstream_block, port=upstream_output_port)
# Returns a GetBlockOutputResult envelope. Use upstream_output.ref for lineage.
get_lineage(ref=upstream_output.ref)
# Returns ancestors: [{producer_block: A, producer_port: out, ...}, ...]
Cross-reference with get_block_logs(run_id, A). The actual fault
might be in A even though B is the one that failed.
5. Common error signatures
| Error string | Root cause | Next tool call |
|---|---|---|
FileNotFoundError: 'data/...' | Config path wrong / file missing | list_data then fix config |
MemoryError | Input too large | Recommend chunking; inspect_data(ref) to confirm size |
KeyError: '<port>' in run | Edge not wired to required input | get_block_schema; fix workflow YAML |
pydantic.ValidationError | Config doesn't match schema | get_block_schema; fix workflow YAML config |
ImportError: ... | Stale registry / missing dep | reload_blocks; check blocks/*.py imports |
subprocess.TimeoutExpired | AI block ran longer than timeout_sec | Increase timeout_sec or simplify prompt |
Type mismatch: expected X got Y | Wrong edge type | list_types; rewire workflow |
Cycle detected in DAG | Edge points to ancestor | Re-draw the YAML DAG |
If the error does not match any row, read the full traceback and report the bottom-most call site to the user. Do not guess.
6. lineage.db — use the MCP surface
Lineage data lives in .scistudio/lineage.db (SQLite). Do NOT
query that file directly. The MCP tools are the public surface:
get_lineage(ref)— ancestors of one refget_block_output(run_id, block_id, port)—GetBlockOutputResultby addressget_run_status(run_id)— top-level run envelope
These tools enforce schema versioning and access semantics. Direct SQLite queries bypass those checks and may return stale or mis-shaped rows.
7. Working inside an AI block PTY
When a workflow's AIBlock step is reached, the engine spawns an
embedded agent in a PTY tab and sets the environment variable
SCISTUDIO_AI_BLOCK_RUN_DIR. If that variable is set in your shell
environment, you are inside an AI block — the workflow is paused
waiting for you to finish.
Canonical termination: when the prompt's work is complete, call
mcp__scistudio__finish_ai_block(run_id, output_refs) where:
run_id— the parent workflow's run id (read fromSCISTUDIO_AI_BLOCK_RUN_IDor the environment).output_refs— a dict{port_name: ref}matching the AIBlock's declaredoutput_ports.
The runtime validates the refs against the block's port types, records the completion in lineage.db, and resumes the downstream workflow.
Failure modes:
finish_ai_blockcalled outside an AI block (noSCISTUDIO_AI_BLOCK_RUN_DIR) — fails fast with a clear error.finish_ai_blockcalled with output refs that don't match the declared port types — runtime rejects with a type-mismatch error; fix the upstream block or the port declaration.- AI block timed out (
subprocess.TimeoutExpired) — the parent workflow already failed; you can no longerfinish_ai_block. Surface to the user and let them re-run with a largertimeout_sec.
8. Worked example
User: "My segmentation workflow failed."
# Step 1: full envelope
get_run_status(run_id="r-abc123")
# → GetRunStatusResult(state="failed",
# progress={"block_states": {"load": "succeeded", "thr": "failed"}},
# errors=[BlockErrorEntry(block_id="thr",
# summary="Block 'thr' raised: type mismatch",
# error="<full traceback>")])
# Step 2: per-block logs
get_block_logs(run_id="r-abc123", block_id="thr")
# stderr → "TypeError: ThresholdSimple.process_item() expected
# Image, got DataFrame"
# Step 3: confirm the upstream output type
load_output = get_block_output(run_id="r-abc123", block_id="load", port="images")
# -> {ref: {...}, type: {type_chain: [...], type_name: "DataFrame"}, produced_at: ""}
inspect_data(ref=load_output.ref)
# → {type: "DataFrame", shape: ..., axes: ...}
# Diagnosis: the workflow YAML wired load's "tables" port (DataFrame)
# into thr's "image" port (Image). Should have wired "images" port.
# Report to user: cite the log line verbatim; recommend fixing the
# workflow YAML edge. Do NOT silently retry.
Mandatory rules
- Always read the FULL
get_run_statusenvelope, not juststate. - For any failed block, ALWAYS call
get_block_logsbefore guessing the cause. - Do NOT speculate — cite log lines verbatim when explaining the failure.
- Do NOT call
cancel_runon arunningworkflow without user confirmation. - Do NOT query
lineage.dbdirectly; use the MCP lineage tools.
Anti-patterns
- Cancelling a still-running workflow without checking with the user.
- Speculating about the cause without reading logs.
- Calling
inspect_dataon a 50 GB array (usepreview_data— it returns a thumbnail). - Re-running the workflow without changing anything ("maybe it'll work this time").
- Stopping at
get_run_statuswithout drilling into per-block logs.
Signals
- GitHub stars
- 30
- Forks
- 1
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
scistudio-debug-run- Source
- github.com/jiazhenz026/scistudio