Skip to content

[Bug]: Boost corrupts non-TTY/piped/redirected output → silent failures in scripts #43

Description

@sverdlov93

Summary

Boost mutates a wrapped command's stdout (multi-line compression + a human-readable [Boost compressed N lines … boost retrieve <id>] marker) even when stdout is not a TTY — i.e. when it is piped, redirected to a file, or captured in a command substitution $(...). This silently corrupts output that another process consumes, with no error and no signal, breaking ordinary shell scripting and CLI automation.

Separate from #42 (the docker shim argv bug), but same root theme: boost altering a command's behavior in a context where it shouldn't.

Impact (real incident)

While investigating a CI issue I ran a standard log-fetch loop:

for jid in $(gh api repos/OWNER/REPO/actions/runs/$RUNID/jobs --jq '.jobs[].id'); do
  gh api repos/OWNER/REPO/actions/jobs/$jid/logs > logs/$jid.log
done
# -> fetched 0 logs. No error. Silent.

It produced zero usable logs. There was no error message and no indication boost was involved. I only found the cause by re-running the inner command with DISABLE_BOOST=1, which showed the data was actually there. Over the rest of the investigation I had to prefix 123 gh/git data-fetch commands with DISABLE_BOOST=1 to get reliable output (full inventory below).

Failure modes

When the wrapped command's stdout feeds another consumer, boost's compression/marker corrupts it:

  • Command substitutionfor x in $(gh ... --jq '.jobs[].id') → the value list is replaced/garbled by the marker → the loop iterates nothing.
  • Redirection to a filegh ... /logs > file → the file receives the [Boost compressed …] marker instead of the actual log → every downstream grep/parse finds nothing.
  • jq / structured pipelinesgh ... --json ... --jq ... → parsed fields no longer match the real output.

Root cause

Boost applies its interactive output transformation unconditionally, regardless of whether stdout is a terminal. Compression and the human-readable marker are only meaningful for an interactive reader; when stdout is a pipe/file/substitution they are pure corruption.

Expected behavior

  1. Auto-detect non-TTY stdout and pass through raw. If isatty(stdout) is false (piped, redirected, captured), boost must emit the original bytes unmodified — no compression, no marker.
  2. Never inject the [Boost compressed …] marker into a byte stream another process consumes. The marker belongs only in interactive/agent-terminal output.
  3. Fail loud, not silent. If boost ever alters output that turns out to be consumed programmatically, there should be a detectable signal rather than silently empty/garbled results.

Why the existing opt-out is insufficient

DISABLE_BOOST=1 <command> works, but you only reach for it after a confusing silent failure — by definition you don't know boost is the cause until you've already lost time debugging. Default-safe pass-through on non-TTY stdout removes the need for the per-command opt-out entirely.


Appendix — complete inventory of boost-disabled commands

During this single investigation I disabled boost 123 times. Every case is a command whose stdout was consumed programmatically (command substitution, pipe to jq/sort/grep, or redirect to a file). Breakdown by command shape (from the session transcript):

Command shape Count Consumption pattern
gh api .../actions/runs/... 28 --jq + $(...)
gh run view ... --json ... --jq 25 $(...) / JSON parse
gh run list ... --json ... --jq 23 $(...) / | sort | uniq -c
gh api .../actions/jobs/$id/logs 22 redirect > file, then grep/sed
gh api .../runs/$id/jobs --jq 3 $(...) loop over job IDs
gh api .../actions/runners --jq 1 JSON parse
gh api .../fly-desktop/.../jobs --jq 1 JSON parse
gh api .../boost/contents/... 1 JSON parse
gh issue create / gh issue comment / gh label list 4 URL/label parse
git log ... --pretty=... 4 parse commit dates/SHAs
git grep -nE ... 1 parse matches
echo "DISABLE_BOOST=1" >> $GITHUB_ENV 1 tested as a mitigation in CI — did not fix the shim

Verbatim examples

1. The silent-failure loop (WITHOUT boost) — fetched 0 logs, no error:

for jid in $(gh api repos/jfrog/fly-service/actions/runs/$RUNID/jobs --paginate --jq '.jobs[].id'); do
  gh api repos/jfrog/fly-service/actions/jobs/$jid/logs 2>/dev/null > mx/$jid.log
done
# → ls mx/*.log: no matches; downstream grep found nothing

2. Same loop WITH DISABLE_BOOST=1 — works correctly:

for jid in $(DISABLE_BOOST=1 gh api repos/jfrog/fly-service/actions/runs/$RUNID/jobs --jq '.jobs[].id'); do
  DISABLE_BOOST=1 gh api repos/jfrog/fly-service/actions/jobs/$jid/logs > mx/$jid.log 2>/dev/null
done

3. Polling run status (command substitution):

runid=$(DISABLE_BOOST=1 gh run list --repo jfrog/fly-service \
  --workflow 'Runner Docker Diagnostics' --limit 1 --json databaseId --jq '.[0].databaseId')

st=$(DISABLE_BOOST=1 gh run view "$runid" --repo jfrog/fly-service --json status --jq '.status')

4. Structured JSON extraction (jq):

DISABLE_BOOST=1 gh run view "$runid" --repo jfrog/fly-service \
  --json headSha,conclusion --jq '{sha:.headSha[0:8],conclusion}'

DISABLE_BOOST=1 gh run view "$runid" --repo jfrog/fly-service \
  --json jobs --jq '.jobs[] | "\(.conclusion) - \(.name)"'

5. Failure-rate tally (pipe to sort | uniq):

DISABLE_BOOST=1 gh run list --repo jfrog/fly-service \
  --workflow ai-evaluation-tests.yml --branch master --limit 40 \
  --json conclusion --jq '.[].conclusion' | sort | uniq -c

6. git history parsing:

DISABLE_BOOST=1 git log --oneline -15 --date=short --pretty='%h %ad %s'
DISABLE_BOOST=1 git log --diff-filter=A --date=short --pretty='%ad %s' -- <path>
DISABLE_BOOST=1 git grep -nE '<pattern>'

7. Mitigation test that FAILED (for cross-reference with #42):

# exported into a GitHub Actions job before jfrog/fly-action@v1
- run: echo "DISABLE_BOOST=1" >> "$GITHUB_ENV"
# fly-action still failed — the runner launches actions via boost_node and the
# /tmp/boost/tools PATH shims remain in effect, so the env toggle never reached
# the spawned process. (See #42.)

Key takeaways for triage

  • The corruption is silent — empty/garbled results with exit code 0 and no stderr.
  • It affects every non-interactive consumption path: $(...), | jq, | sort, > file.
  • DISABLE_BOOST=1 is a reliable per-command workaround but is only discoverable after a failure.
  • The single highest-value fix: pass through unmodified when stdout is not a TTY.

Environment

  • Boost binary: /tmp/boost/tools/boost-arm64, statically-linked ELF aarch64, BuildID 397bf566827c6454219a2eba05fcdec8c27c2b2c (rebuilt 2026-06-18)
  • Wrapped commands affected in practice: gh api, gh run, gh issue, gh label, git log, git grep

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions