scistudio-debug-run

SkillMonitoring & ops

Use 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.

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; stdout holds whatever the script printed.
  • run_log — the per-run engine log filtered to this block. Everything the block wrote arrives in stderr; stdout is 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's config.path points 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' inside run(self, inputs, config) — the block expected an input port X that was not wired. Check the workflow YAML edges.
  • pydantic.ValidationError in block config — config_schema mismatch. Check the workflow YAML's node config against the block's schema (get_block_schema).
  • ImportError — stale reload_blocks or a missing dependency in the block's Python file.
  • subprocess.TimeoutExpired — AI block timed out; config.timeout_sec was 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 stringRoot causeNext tool call
FileNotFoundError: 'data/...'Config path wrong / file missinglist_data then fix config
MemoryErrorInput too largeRecommend chunking; inspect_data(ref) to confirm size
KeyError: '<port>' in runEdge not wired to required inputget_block_schema; fix workflow YAML
pydantic.ValidationErrorConfig doesn't match schemaget_block_schema; fix workflow YAML config
ImportError: ...Stale registry / missing depreload_blocks; check blocks/*.py imports
subprocess.TimeoutExpiredAI block ran longer than timeout_secIncrease timeout_sec or simplify prompt
Type mismatch: expected X got YWrong edge typelist_types; rewire workflow
Cycle detected in DAGEdge points to ancestorRe-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 ref
  • get_block_output(run_id, block_id, port)GetBlockOutputResult by address
  • get_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 from SCISTUDIO_AI_BLOCK_RUN_ID or the environment).
  • output_refs — a dict {port_name: ref} matching the AIBlock's declared output_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_block called outside an AI block (no SCISTUDIO_AI_BLOCK_RUN_DIR) — fails fast with a clear error.
  • finish_ai_block called 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 longer finish_ai_block. Surface to the user and let them re-run with a larger timeout_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_status envelope, not just state.
  • For any failed block, ALWAYS call get_block_logs before guessing the cause.
  • Do NOT speculate — cite log lines verbatim when explaining the failure.
  • Do NOT call cancel_run on a running workflow without user confirmation.
  • Do NOT query lineage.db directly; 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_data on a 50 GB array (use preview_data — it returns a thumbnail).
  • Re-running the workflow without changing anything ("maybe it'll work this time").
  • Stopping at get_run_status without 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