Skip to content

server: join tool-result parts with array_to_string, not tera - #100

Closed
asubbarao wants to merge 1 commit into
mainfrom
server/blocks-join-without-tera
Closed

asubbarao wants to merge 1 commit into
mainfrom
server/blocks-join-without-tera

Conversation

@asubbarao

Copy link
Copy Markdown
Owner

Why

Dev aborted twice on 2026-10-21 (13:57 and 13:58), each time about 60 s after boot, while blocks_refresh posted the parsed view over the transcripts. The server log shows TeraRenderFunc under a CASE in a projection, then panicked at src/lib.rs:135:50 … NulError and thread caused non-unwinding panic. aborting. The cause is server/blocks.sql line 50: it joined a tool result's text parts with tera_render('{{ parts | join(sep="\n") }}', …). A part with a NUL byte kills the server; the template never did anything a list join does not.

Change

One expression: array_to_string(text_parts, chr(10)) in place of the tera join. The comment names the reason.

Tested

Local duckdb :memory: with the tera extension loaded:

  • tera_render on ['a', 'b' || chr(0) || 'c'] → NulError(3, [97, 10, 98, 0, 99]), process exit 134. array_to_string on the same list → 5 chars, NUL kept.
  • tera_render(...) = array_to_string(parts, chr(10)) is true for ['a','b'], a multi-line part with a quoted part, ['é', ''] and ['one'].

🤖 Generated with Claude Code

A tool result part holding a NUL byte made tera_render panic (NulError at src/lib.rs:135) and the panic cannot
unwind, so the whole dev server aborted: twice on 2026-10-21, 13:57 and 13:58, about 60 s after each boot when
blocks_refresh posted the parsed view over the transcripts. The join never needed a template; array_to_string
gives the same text (checked on multi-line, quoted, unicode and empty parts) and keeps the NUL.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@asubbarao
asubbarao enabled auto-merge October 21, 2026 21:32
@asubbarao

Copy link
Copy Markdown
Owner Author

duckstack/review: error

The review did not start; the next tick starts it again. Dev's answer:

Invalid Input Error: Pipe process exited abnormally code=1: cd '/Users/aloksubbarao/.duck/deploy' && PR_BASE=$(git -C /Users/aloksubbarao/duckdb-skills merge-base origin/main d79b5c9) PR_REPO='/Users/aloksubbarao/duckdb-skills' PR_GH='/opt/homebrew/bin/gh' PR_GH_REPO

Opus 5.5, duckstack review queue (duckstack-review skill)

@asubbarao

Copy link
Copy Markdown
Owner Author

Closed on Alok's decision: the NUL byte came from an agent reading a binary file (pdf.duckdb_extension.info) with cat into its transcript. The fix is that agents read binary files with read_blob, never as text; not a change to this join. — Claude Fable 5.1

@asubbarao asubbarao closed this Oct 21, 2026
auto-merge was automatically disabled October 21, 2026 21:43

Pull request was closed

@asubbarao
asubbarao deleted the server/blocks-join-without-tera branch October 21, 2026 21:43
@asubbarao
asubbarao restored the server/blocks-join-without-tera branch October 21, 2026 21:53
@asubbarao

Copy link
Copy Markdown
Owner Author

Reopened on Alok's call: no DuckDB reader breaks on a NUL byte (read_json, read_csv and array_to_string all carry it, tested); only the tera_render join in blocks.sql aborts the server on it. The query is the fault. — Claude Fable 5.1

@asubbarao asubbarao reopened this Oct 21, 2026
@asubbarao
asubbarao enabled auto-merge October 21, 2026 21:54
@asubbarao

Copy link
Copy Markdown
Owner Author

duckstack/review: failure

  • server/blocks.sql:50 RULES.md 15: The fix uses array_to_string, which rule 15 bans by name (the hook is meant to block it). The NUL-byte panic is a good reason to stop using tera_render, but it doesn't justify array_to_string: rule 15's other option, string_agg, does the same join without tera.
    Rewrite: coalesce(CASE WHEN len(text_parts) > 0 THEN list_aggregate(text_parts, 'string_agg', part_separator) END,
    CASE WHEN json_type(b) = 'VARCHAR' THEN b ->> '$' END, [b ->> k FOR k IN content_keys IF b ->> k IS NOT NULL][1]) AS message_content,
  • server/blocks.sql:50 RULES.md 12, RULES.md 7: The separator is built as chr(10) in the middle of an expression. Rule 12 bans that, and rule 7 says values like this are declared up front in the first CTE.
    Rewrite: -- in the line CTE, beside part_keys:
    E'\n' AS part_separator,
    -- in parsed, add it to the EXCLUDE list:
    SELECT * EXCLUDE (b, content_keys, id_keys, part_keys, message_types, part_separator), ...
    -- and join with it:
    list_aggregate(text_parts, 'string_agg', part_separator)

Opus 5.5, duckstack review queue (duckstack-review skill)

@asubbarao

Copy link
Copy Markdown
Owner Author

Closed as a duplicate of #101, which strips the NUL with replace(…, chr(0), '') as Alok said. — Claude Fable 5.1

@asubbarao asubbarao closed this Oct 21, 2026
auto-merge was automatically disabled October 21, 2026 22:08

Pull request was closed

@asubbarao
asubbarao deleted the server/blocks-join-without-tera branch October 21, 2026 22:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant