langchain-ai/deepagents · error · ValueError
row has {len} cells, expected {expected}
Error message
row has {len} cells, expected {expected} What it means
`_markdown_table` renders rows into a Markdown table and refuses ragged rows: if any row's cell count differs from the header count, it raises this ValueError, because Markdown silently drops surplus cells and pads missing ones — corrupting the table with no diagnostic.
Source
Thrown at libs/code/deepagents_code/app.py:2459
Every header and cell is escaped via `_escape_markdown`, so external text
can neither forge additional cells with `|` nor be parsed as markdown.
Args:
headers: Column headings.
rows: One sequence of cells per row, each matching `headers` in length.
Returns:
The table as markdown source.
Raises:
ValueError: If a row's length does not match `headers`. Checked because
markdown silently drops surplus cells and pads missing ones, so a
ragged row would corrupt the table with no other diagnostic.
"""
for row in rows:
if len(row) != len(headers):
msg = f"row has {len(row)} cells, expected {len(headers)}"
raise ValueError(msg)
lines = [
"| " + " | ".join(_escape_markdown(header) for header in headers) + " |",
"| " + " | ".join("---" for _ in headers) + " |",
]
lines.extend(
"| " + " | ".join(_escape_markdown(cell) for cell in row) + " |" for row in rows
)
return "\n".join(lines)
def _log_task_exception(task: asyncio.Task[Any]) -> None:
"""Done-callback that surfaces unhandled exceptions from fire-and-forget tasks.
Default `asyncio` behavior is to log "Task exception was never retrieved"
only when the task is GC'd — easy to miss. This callback runs at task
completion and routes failures through `logger.warning` with `exc_info`,
matching the codebase pattern at `_finalize_git_branch_refresh`. Use
when scheduling a coroutine via `asyncio.create_task` whose result isView on GitHub (pinned to a1af029e6e)
Solutions
- Pad short rows to the header length and truncate long rows before calling the renderer
- Fix the data source so every row has exactly len(headers) cells
- Assert row lengths in a unit test when constructing rows dynamically
Example fix
// before
rows = [("a", "b"), ("c",)]
_markdown_table(headers, rows)
// after
rows = [tuple(row) + ("",) * (len(headers) - len(row)) if len(row) < len(headers) else row[:len(headers)] for row in rows]
_markdown_table(headers, rows) Defensive patterns
Strategy: validation
Validate before calling
if any(len(row) != len(headers) for row in rows):
raise ValueError("ragged table rows before rendering") Type guard
def is_rectangular(headers: list[str], rows: list[list[str]]) -> bool:
return all(len(row) == len(headers) for row in rows) Try / catch
try:
table = _markdown_table(headers, rows)
except ValueError as exc:
logger.error("table render failed: %s", exc)
table = "" # or fall back to a list rendering Prevention
- Normalize rows (pad/truncate) against len(headers) before rendering
- Keep data sources producing fixed-width tuples
- Add a unit test asserting rectangularity for dynamic catalogs
- Check for schema drift when upstream models add/remove fields
When it happens
Trigger: Calling `_markdown_table` (directly or via `_render_extensions` / `_render_tool_catalog`) with a row list whose lengths vary or don't match `len(headers)` — e.g. a source returning a variable-arity tuple per row.
Common situations: Extension metadata or tool catalog data where one entry gained/lost a field; joining two data sources with different column counts; optional trailing fields omitted in one row.
AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29).
Data as JSON: /api/errors/e1d63a41e8f2768f.
Report an issue: GitHub.