cd /news/ai-tools/i-kept-hitting-jq-parse-error-on-ai-… Β· home β€Ί topics β€Ί ai-tools β€Ί article
[ARTICLE Β· art-151047] src=dev.to β†— pub= topic=ai-tools verified=true sentiment=↑ positive

I kept hitting jq: parse error on AI streams, so I built sjq

A developer built sjq, an MIT-licensed Rust CLI that parses OpenAI-compatible server-sent event streams from /v1/chat/completions and /v1/responses, printing one short line per event showing only the delta content instead of the full JSON envelope. The tool renders tool calls, reasoning_content, refusals, logprobs, usage blocks and multiple choices, and adds timing output including TTFT, total time, tokens/sec, plus connect, TLS handshake and TTFB when run through sjq curl. It ships 14 test fixtures covering multi-line data payloads, mid-stream errors, parallel tool calls, logprobs and CJK/emoji text.

by read2 min views1 publishedOct 16, 2026

If you work with LLMs, you've done this: curl -N <streaming endpoint> | jq. And you've seen this:

jq: parse error: Invalid numeric literal at line 1, column 9

Column 9. That's where data: ends and the JSON begins. An SSE stream isn't JSON β€” it's a framing protocol: data: {...} lines separated by blank lines, [DONE] at the end. jq never stood a chance.

So I built sjq β€” jq, but for AI streaming. A small Rust CLI that parses OpenAI-compatible server-sent event streams (/v1/chat/completions and /v1/responses) and prints one short line per event, showing only what each event added.

sjq curl -s $BASE/v1/chat/completions \
  -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -d '{"model":"gemini-4-argon","stream":true,"messages":[{"role":"user","content":"hi"}]}'
model: gemini-4-argon
role: assistant
data: 'Hi'
data: ' there'
data: ' πŸ‘‹'
…
time: ttft=0.42s total=3.1s tok/s=258.2

Instead of 99% envelope JSON, you watch the actual content arrive β€” plus a timing line: TTFT, total, tokens/sec. Run through sjq curl and it also breaks out connect, TLS handshake, and TTFB, which is genuinely useful when you're debugging a slow provider.

It understands tool calls (name + arguments as they stream in), reasoning_content, refusals, logprobs, usage blocks, and multiple choices β€” each rendered as its own labeled line.

brew tap vivgrid/tap
brew trust vivgrid/tap  # Homebrew 6+ requires trusting third-party taps
brew install sjq

Or from source: cargo install --git https://github.com/vivgrid/sjq (one dependency: serde_json).

You can β€” sed 's/^data: //' | jq β€” and plenty of people do. But then you're staring at full envelope objects per chunk: ids, timestamps, object: "chat.completion.chunk", finish_reason: null fifty times over. The signal (the tokens) drowns in the envelope. sjq inverts that: the delta is the output; the envelope gets summarized once.

MIT licensed, single binary. The repo ships 14 test fixtures covering the gnarly edges: multi-line data payloads, mid-stream errors, parallel tool calls, logprobs, CJK/emoji text.

Repo: https://github.com/vivgrid/sjq β€” feedback and weird streams welcome.

── more in #ai-tools 4 stories Β· sorted by recency
── more on @sjq 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/i-kept-hitting-jq-pa…] indexed:0 read:2min 2026-10-16 Β· β€”