Debug Stages — Persistencia stage-level y flag --verbose
Origen: Claude Code / claude-sonnet-4-6 Última actualización: 2026-04-25
:::note Cambio SDD
Documentación introducida el 2026-04-25 con el cambio SDD debug-stages.
Archivado en openspec/changes/archive/2026-04-25-debug-stages/.
:::
Contexto: el problema previo
Sección titulada «Contexto: el problema previo»Antes de este cambio, el objeto PipelineRun almacenaba en su campo stages la información stage-level completa — status, duración, métricas y errores. Sin embargo, esa información nunca se persistía en disco: solo se guardaba el status top-level del pipeline en la tabla pipeline_runs.
Al investigar un fallo días después, la base de datos solo mostraba “pipeline falló” sin indicar en qué stage ocurrió el problema, cuánto duró, ni qué error se lanzó. Además, el flag debug=True estaba hardcodeado en 7 archivos de dominio, ignorando por completo el flag --verbose del CLI.
Solución
Sección titulada «Solución»Tabla pipeline_stage_runs
Sección titulada «Tabla pipeline_stage_runs»Nueva tabla en SALIDAS/db/pipelines.db que persiste el resultado de cada stage tras pipe.run().
DDL:
CREATE TABLE IF NOT EXISTS pipeline_stage_runs ( id INTEGER PRIMARY KEY AUTOINCREMENT, run_id INTEGER NOT NULL, stage TEXT NOT NULL, status TEXT NOT NULL, duration_s REAL, metrics_json TEXT, error_json TEXT, created_at TEXT DEFAULT (datetime('now')));Columnas:
| Columna | Tipo | Descripción |
|---|---|---|
id | INTEGER | PK autoincrement |
run_id | INTEGER | FK lógica a pipeline_runs.id |
stage | TEXT | Nombre del stage (ej. "fetch", "process", "publish") |
status | TEXT | "ok", "failed" — stages "skipped" no se insertan |
duration_s | REAL | Duración en segundos; NULL si no disponible |
metrics_json | TEXT | JSON serializado con métricas del stage (filas procesadas, etc.) |
error_json | TEXT | JSON serializado con info del error; NULL si status = "ok" |
created_at | TEXT | Timestamp UTC de inserción |
Ejemplo de fila típica:
id=42, run_id=7, stage="process", status="failed",duration_s=3.14, metrics_json=null,error_json='{"type": "KeyError", "msg": "Column HES_NUM missing"}'Helper _is_debug_mode(extras)
Sección titulada «Helper _is_debug_mode(extras)»Ubicación: core/utils.py (módulo nuevo agregado el 2026-04-25)
Firma:
def _is_debug_mode(extras: dict) -> bool:Comportamiento:
- Lee
extras.get("verbose", False)— si el flag--verbosefue pasado al CLI,extraslo contiene. - Si el valor es
Falseo la clave no existe, hace fallback a la variable de entornoINGEL_DEBUG. - Retorna
Truesi cualquiera de las dos fuentes indica modo debug.
Uso en los dominios: Los 7 archivos que tenían debug=True hardcodeado fueron actualizados para llamar _is_debug_mode(self.config.extras):
domains/pedidos_hes/pipeline.pydomains/pedidos_sap/pipeline.pydomains/facturacion/pipeline.pydomains/valorizaciones/pipeline.pydomains/gantt/pipeline.pycore/ingest_imap.py- (un séptimo dominio según el scope del cambio)
Hook en el orquestador
Sección titulada «Hook en el orquestador»En run_pipeline_v2.py, tras llamar update_pipeline_run(run_id, ...), se invoca:
insert_stage_runs(run_id, pipeline_run)Reglas de inserción:
- Stages con
status = "skipped"se omiten — sin valor diagnóstico. - Si
insert_stage_runsfalla internamente (ej. error de DB), no interrumpe el pipeline: el error se captura en untry/exceptinterno y se loggea sin propagar.
Cómo usar para debugging
Sección titulada «Cómo usar para debugging»Ver la última falla por pipeline
Sección titulada «Ver la última falla por pipeline»SELECT pr.pipeline_name, psr.stage, psr.status, psr.duration_s, psr.error_jsonFROM pipeline_runs prJOIN pipeline_stage_runs psr ON psr.run_id = pr.idWHERE pr.status = 'failed'ORDER BY pr.id DESCLIMIT 10;Stage que más tarda en promedio
Sección titulada «Stage que más tarda en promedio»SELECT stage, AVG(duration_s) AS avg_s, COUNT(*) AS nFROM pipeline_stage_runsWHERE status = 'ok'GROUP BY stageORDER BY avg_s DESC;Ver stages de un run específico
Sección titulada «Ver stages de un run específico»SELECT stage, status, duration_s, error_jsonFROM pipeline_stage_runsWHERE run_id = <ID>ORDER BY id ASC;Activar verbose por env var (sin tocar CLI)
Sección titulada «Activar verbose por env var (sin tocar CLI)»# Unix / Git Bashexport INGEL_DEBUG=1python run_pipeline_v2.py pedidos_HES
# Windows CMDset INGEL_DEBUG=1python run_pipeline_v2.py pedidos_HESActivar verbose por flag CLI
Sección titulada «Activar verbose por flag CLI»python run_pipeline_v2.py --all --verbosepython run_pipeline_v2.py facturacion --verboseLimitaciones conocidas
Sección titulada «Limitaciones conocidas»- Stages
skippedno se insertan — decisión de diseño: no tienen valor diagnóstico. metrics_jsonyerror_jsonson texto JSON serializado; para queries estructuradas se requierejson_extract()de SQLite.- T6.1 out-of-scope: el plan original incluía anotar el contexto de fila/correo en los errores (qué dato específico causó el fallo). Los loops de procesamiento aún no anotan eso — queda como follow-up.
- No hay FK formal entre
pipeline_stage_runs.run_idypipeline_runs.id— la relación es lógica, no DDL.
Ver también
Sección titulada «Ver también»- Orquestador-Pipelines — flags CLI (
--verbose) y estructura de waves - DB-Diccionario-Tablas — esquema completo de las DBs SQLite
- Decisiones-Tecnicas — historial de ADRs
- GSheets-Consistency-Publish — PUBLISH integrado y tabla
publish_log