skills/notebook-executor/SKILL.md
Execute Jupyter notebooks end-to-end with SQL pre-validation, error diagnosis, and auto-fix loops. Use when "run notebook", "execute notebook", "test notebook", or "validate notebook execution".
npx skillsauth add OliverOuyang/shuhe-work-skills notebook-executorInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
A 3-phase pipeline: Validate SQL -> Execute Notebook -> Diagnose & Fix Errors.
Does NOT overlap with:
sql-runner (submits individual SQL queries to Dataphin)notebook-standardizer (restructures/formats notebooks, not executes them)/notebook-executor path/to/notebook.ipynb [--skip-sql-check] [--timeout=900] [--max-retries=3]
| Argument | Default | Description |
|----------|---------|-------------|
| notebook_path | (required) | Path to the .ipynb file |
| --skip-sql-check | false | Skip Phase 1 SQL pre-validation |
| --timeout | 900 | Execution timeout in seconds (SQL queries can take 5+ min) |
| --max-retries | 3 | Maximum fix-and-retry iterations |
Before running the notebook, validate SQL files it depends on.
Scan notebook code cells for ALL SQL reference patterns. Real notebooks use many styles:
import nbformat, re, ast
nb = nbformat.read(notebook_path, as_version=4)
sql_files = []
for cell in nb.cells:
if cell.cell_type == 'code':
src = cell.source
# Pattern 1: load_sql_file(..., "path.sql") or pipe.run_file(..., "path.sql", ...)
# Greedy scan past os.path.join() and other intermediate arguments
sql_files += re.findall(r'(?:load_sql_file|run_file)\s*\([^)]*["\']([^"\']+\.sql)["\']', src)
# Pattern 2: os.path.join(SQL_DIR, "name.sql") — extract the .sql filename
sql_files += re.findall(r'os\.path\.join\s*\([^)]*["\']([^"\']+\.sql)["\']', src)
# Pattern 3: Variable dict/list building — {"label": ("filename.sql", ...)}
sql_files += re.findall(r'["\']([^"\']+\.sql)["\']', src)
# Pattern 4: pipe.run_files_parallel([...]) — list of (path, params) tuples
if 'run_files_parallel' in src:
sql_files += re.findall(r'["\']([^"\']*\.sql)["\']', src)
# Deduplicate while preserving order
sql_files = list(dict.fromkeys(sql_files))
The regex patterns above catch static string literals. Real notebooks also use dynamic patterns that need manual resolution:
f"{_dname}.sql" in a for _dname in ["name1", "name2"] loop — trace the loop variable to get actual filenames_fname from {"label": ("filename.sql", params)} — extract the .sql values from the data structurepipe.run_file(os.path.join(SQL_DIR, _fname), ...) where _fname is a variable — trace back to its string literal assignmentIf regex finds {variable}.sql patterns or pipe.run_file(variable) calls, trace the variable assignment in the same cell to resolve actual filenames.
Fallback: If no .sql literals are found but the notebook references SQL_DIR, check the sql/ directory for all .sql files and cross-reference with the notebook's import/execution patterns.
For each SQL file:
FROM / JOIN clausesmcp__sh_dp_mcp__get_dp_table_meta to verify tables exist and fields are correctSELECT * (should be explicit columns)Submit each SQL with LIMIT 10 appended via mcp__sh_dp_mcp__submit_dp_query to verify syntax without fetching full data.
Print [OK] / [WARN] per SQL file. Example:
[Phase 1] SQL Pre-validation
[OK] 定向配置分析_By月.sql -- 24 columns verified
[OK] 定向配置_排除包明细.sql -- 6 columns verified
[WARN] 定向配置_地域明细.sql -- field 'xxx' not found
Run the notebook and capture results.
Before executing, check for and remove any stale _test_run.ipynb from a previous run:
# _test_run.ipynb is written NEXT TO the input notebook (--output is relative to input dir)
TEST_OUTPUT="{notebook_dir}/_test_run.ipynb"
if [ -f "$TEST_OUTPUT" ]; then
rm -f "$TEST_OUTPUT" 2>/dev/null || echo "[WARN] Cannot delete stale _test_run.ipynb — Jupyter kernel may have it locked. Ask user to close it."
fi
jupyter nbconvert --to notebook --execute "{notebook_path}" \
--output "_test_run.ipynb" \
--ExecutePreprocessor.timeout={timeout}
Path note: --output "_test_run.ipynb" writes the file relative to the input notebook's directory, not the CWD. So if the notebook is at C:/project/analysis.ipynb, the output is C:/project/_test_run.ipynb.
Default timeout: 900 seconds (SQL queries can take 5+ minutes).
Check both the exit code AND the output file existence:
import os
test_output = os.path.join(os.path.dirname(notebook_path), "_test_run.ipynb")
# Exit code 0 but no output file = silent failure
if not os.path.exists(test_output):
print("[FAIL] jupyter nbconvert exited but produced no output file")
# Check if notebook has a kernel spec issue or empty cell list
Windows note: CreateFile() Error: 5 in terminal output is a Windows terminal artifact, NOT a real error. Ignore it.
After execution, inspect the output notebook for errors:
import nbformat
# Read from the CORRECT path (next to input notebook, not CWD)
test_output = os.path.join(os.path.dirname(notebook_path), "_test_run.ipynb")
nb = nbformat.read(test_output, as_version=4)
errors = []
for i, cell in enumerate(nb.cells):
if cell.cell_type == 'code' and cell.get('outputs'):
for out in cell.outputs:
if out.get('output_type') == 'error':
errors.append((i, cell.get('id', ''), out['ename'], out['evalue']))
If no errors: report [OK] All N cells passed, clean up _test_run.ipynb, done.
If errors found: proceed to Phase 3.
When errors are found, match against known patterns and apply targeted fixes.
| Pattern | Root Cause | Auto-Fix |
|---------|-----------|----------|
| TypeError: Cannot use method 'nlargest' with dtype object | Dataphin returns all columns as string. DataFrame numeric columns need conversion. | Add pd.to_numeric(df[col], errors='coerce') before the failing operation |
| KeyError: '{column_name}' | Column was renamed/removed in SQL refactoring but notebook cell still references old name. | Search notebook for the missing column, identify correct replacement from DataFrame columns |
| ODPS-0420061: data is larger than rendering limitation | SQL returns too much data (usually ARRAY fields expanded). | Rewrite SQL to use SIZE() scalar categories instead of raw ARRAY fields, or add LIMIT, or split into sub-queries |
| ODPS-0130071 | CASE expressions used directly in GROUP BY (ODPS doesn't support this). | Wrap in CTE: pre-compute CASE in WITH clause, GROUP BY column aliases |
| NameError: name 'xxx' is not defined | Cell execution order issue, or variable defined in a cell that was skipped/failed. | Check if variable is defined in a prior cell that may have errored |
| ModuleNotFoundError | Missing Python package. | pip install {package} then retry |
| ValueError: No objects to concatenate | Empty DataFrame from a query that returned no data. | Add empty DataFrame guard: if df.empty: print("[SKIP]"); return |
| AttributeError: 'NoneType' has no attribute | Query returned None instead of DataFrame (timeout or error). | Add null check before operations |
| CreateFile() Error: 5 | Windows terminal artifact from Python subprocess. | Not a real error — ignore it. Check actual cell outputs for real errors |
| Exit code 0 but no _test_run.ipynb | Kernel spec missing, empty notebook, or silent crash. | Check jupyter kernelspec list, verify notebook has code cells |
| ODPS-* SQL timeout / empty result | ds partition wrapped in functions (CONCAT/SUBSTR) preventing partition pruning. | Rewrite WHERE clause to compare ds directly: ds >= '20260101' |
| run_files_parallel failures | Multiple SQL files executed in parallel; one failure can mask others. | Check each SQL result individually, report all failures not just the first |
{notebook_name}.backup.ipynb. Never overwrite the backup._test_run.ipynb — the test output may have corrupted metadata)nbformat.write(nb, original_path)_test_run.ipynb before re-running (avoid Jupyter lock conflicts)1. Parse arguments
2. Phase 1: SQL pre-validation (skip if --skip-sql-check)
3. Phase 2: Execute notebook
4. If success -> report [OK] -> clean up _test_run.ipynb -> done
5. If errors -> Phase 3: diagnose, apply fix, increment retry counter
6. Loop to Phase 2 (max retries)
7. If still failing -> report errors with diagnosis to user
=== Notebook Executor ===
Target: 定向配置分析_By月.ipynb
[Phase 1] SQL Pre-validation
[OK] 定向配置分析_By月.sql -- 24 columns verified
[OK] 定向配置_排除包明细.sql -- 6 columns verified
[WARN] 定向配置_地域明细.sql -- field 'xxx' not found
[Phase 2] Execution (attempt 1/3)
[RUN] jupyter nbconvert --execute ...
[FAIL] Cell 21 (id=82aace27): TypeError: Cannot use method 'nlargest' with dtype object
[Phase 3] Auto-Fix
[FIX] Added pd.to_numeric() conversion for 'plan_cnt' column
[WRITE] Updated cell 15 via nbformat
[Phase 2] Execution (attempt 2/3)
[RUN] jupyter nbconvert --execute ...
[OK] All 38 cells passed
[Cleanup] Removed _test_run.ipynb
| Skill | Purpose | When to Use |
|-------|---------|-------------|
| notebook-executor (this) | Run a notebook end-to-end, diagnose runtime errors, auto-fix and retry | "Run this notebook", "Test if it works", "Fix errors and re-run" |
| notebook-standardizer | Restructure a notebook to follow cell manifest conventions | "Standardize this notebook", "Add proper cell tags" |
| sql-runner | Submit a single SQL query to Dataphin | "Run this SQL", "Check this query" |
Key distinction: notebook-standardizer Step 4 also runs jupyter nbconvert --execute, but only as a final validation after restructuring. If execution fails there, hand off to notebook-executor for diagnosis. The two skills are complementary, not competing.
Use forward slashes in bash commands. Handle Chinese filenames with UTF-8 encoding. Example:
# Correct
jupyter nbconvert --execute "C:/Users/Oliver/Desktop/排除包效果回收_By月.ipynb"
# Incorrect (backslashes cause issues in bash)
jupyter nbconvert --execute "C:\Users\Oliver\Desktop\排除包效果回收_By月.ipynb"
_test_run.ipynb may be locked if Jupyter is running the same notebook in a browser session. Warn user to close the notebook in Jupyter before execution.
Many notebooks have a DATA_MODE = "sql" | "csv" toggle in their CONFIG cell. If SQL validation fails and CSV data files exist, suggest switching to DATA_MODE = "csv" as a fallback.
SQL queries via QueryPipeline can take 2-5 minutes each. A notebook with multiple queries needs generous timeout. Default 900s is appropriate for most cases. For notebooks with 5+ SQL queries, consider --timeout=1800.
Always try to delete _test_run.ipynb after a successful run. If deletion fails (file locked), warn user but do not treat as an error.
Always use nbformat.read(path, as_version=4) and nbformat.write(nb, path) for reading and editing notebooks. Never edit .ipynb JSON directly -- the JSON structure is fragile and easy to corrupt.
When validating or fixing SQL, remember: the ds partition field must be compared directly (ds >= '20260101'). Never wrap ds in functions like CONCAT() or SUBSTR() -- this prevents partition pruning and causes queries to timeout or return empty results.
Tables like pdm_marketing_channel_level_4_ad_config_info_di have ARRAY-typed fields (region, placement, exclude_audience_package). If a query expands these with EXPLODE and hits the ODPS rendering limit, rewrite to use SIZE() for categorization instead.
tools
SQL 分段验证、自我修复、结果导出与智能分析。流程:解析SQL → Dataphin MCP 验证元数据 → 自动修复 → 分段执行验证 → 导出 CSV → 智能分析(漏斗解读、异常识别、预判用户问题)。适用场景:"跑一下这个SQL"、"验证这个查询"、"帮我执行并导出"、"分析一下结果"等。
testing
Security-first vetting for OpenClaw skills. Use before installing any skill from ClawHub, GitHub, or other sources. Checks for red flags, permission scope, and suspicious patterns.
development
A universal self-improving agent that learns from ALL skill experiences. Uses multi-memory architecture (semantic + episodic + working) to continuously evolve the codebase. Auto-triggers on skill completion/error with hooks-based self-correction.
data-ai
Standardize Jupyter notebooks (.ipynb) for interactive data analysis workflows. Enforces a mandatory cell manifest (M1-M8 + archetype chapters) with tags ([CONFIG]/[SETUP]/[FUNC]/[RUN]/[VIZ]/[EXPORT]), structured markdown sections, and output prefixes ([OK]/[WARN]/[SKIP]). Use when the user wants to standardize, clean up, or create a notebook from scratch. Two archetypes: problem-driven (question-answer analysis) and monitoring (dimension-based periodic reporting).