Skip to content

Commit e8d6e98

Browse files
Merge pull request #118 from oasdiff/fix/pr-comment-jq-arg-list-too-long
pr-comment: pipe changes payload via stdin to avoid jq ARG_MAX failure
2 parents c017d5f + e19808f commit e8d6e98

2 files changed

Lines changed: 89 additions & 4 deletions

File tree

.github/workflows/test.yaml

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,3 +581,80 @@ jobs:
581581
exit 1
582582
fi
583583
echo "PASS"
584+
585+
pr_comment_handles_large_payload:
586+
runs-on: ubuntu-latest
587+
name: Test pr-comment jq payload survives a multi-MB changes array
588+
# Regression test for the jq argv ARG_MAX limit. Real-world specs can
589+
# produce changelogs in the multi-MB range; the original implementation
590+
# passed `$changes` via `jq --argjson changes "$changes"` which put the
591+
# entire JSON string on jq's command line and exceeded ARG_MAX (typical
592+
# Linux ceiling is 2 MB), surfacing as `jq: Argument list too long` and
593+
# aborting the action right before the service POST. The fix pipes the
594+
# changes payload via stdin instead. This job stubs oasdiff to emit a
595+
# ~4 MB changelog and asserts the entrypoint reaches the no-token skip
596+
# without aborting, which proves the jq invocation processed the
597+
# payload successfully.
598+
steps:
599+
- uses: actions/checkout@v6
600+
- name: Stub oasdiff to emit a multi-MB changelog
601+
run: |
602+
set -euo pipefail
603+
mkdir -p /tmp/stub
604+
605+
# Pre-build a ~2 MB filler string to embed inside two synthetic
606+
# change entries (~4 MB total payload, well above the 2 MB Linux
607+
# ARG_MAX ceiling). dd reads a bounded number of zero bytes and
608+
# exits 0 cleanly, then tr converts to 'a's. We avoid `yes ... |
609+
# head -c N` because `head` closes the pipe and `yes` exits with
610+
# SIGPIPE, which fails under `set -o pipefail`.
611+
dd if=/dev/zero bs=1024 count=2000 status=none | tr '\0' 'a' > /tmp/filler
612+
filler_size=$(wc -c < /tmp/filler)
613+
echo "filler size: ${filler_size} bytes"
614+
615+
cat > /tmp/stub/oasdiff <<'STUB'
616+
#!/bin/sh
617+
# Emit a synthetic JSON changelog totalling >4 MB. printf is a
618+
# POSIX builtin so neither the variable assignment via $(cat ...)
619+
# nor the final printf '%s' "$json" goes through execve.
620+
filler=$(cat /tmp/filler)
621+
printf '[{"id":"big-1","text":"%s","level":3},{"id":"big-2","text":"%s","level":3}]' "$filler" "$filler"
622+
STUB
623+
chmod +x /tmp/stub/oasdiff
624+
625+
mkdir -p /tmp/run
626+
export GITHUB_REF=refs/pull/123/merge
627+
export GITHUB_REPOSITORY=foo/bar
628+
export GITHUB_SHA=deadbeef
629+
export GITHUB_BASE_REF=main
630+
export GITHUB_STEP_SUMMARY=/tmp/run/step-summary
631+
cat > /tmp/run/event.json <<EVT
632+
{"pull_request":{"head":{"sha":"deadbeef"},"base":{"sha":"baadcafe"}}}
633+
EVT
634+
export GITHUB_EVENT_PATH=/tmp/run/event.json
635+
636+
export PATH=/tmp/stub:$PATH
637+
set +e
638+
out=$(./pr-comment/entrypoint.sh \
639+
'specs/base.yaml' 'specs/revision.yaml' \
640+
'' '' '' '' '' '' 2>&1)
641+
rc=$?
642+
set -e
643+
echo "--- entrypoint output (truncated) ---"
644+
echo "$out" | head -c 2000
645+
echo "--- exit code: $rc ---"
646+
647+
if [ "$rc" -ne 0 ]; then
648+
echo "FAIL: expected exit 0 (script should reach the no-token skip after building the JSON payload), got $rc" >&2
649+
echo "If the message contains 'jq: Argument list too long', the regression has returned: \$changes is being passed via argv (--argjson) instead of stdin." >&2
650+
exit 1
651+
fi
652+
if ! echo "$out" | grep -q "::notice::.*View API changes"; then
653+
echo "FAIL: script aborted before emitting the review-page notice" >&2
654+
exit 1
655+
fi
656+
if ! echo "$out" | grep -q "No oasdiff-token provided"; then
657+
echo "FAIL: script aborted before reaching the no-token skip; the jq payload-construction step likely blew up on the multi-MB changes array" >&2
658+
exit 1
659+
fi
660+
echo "PASS"

pr-comment/entrypoint.sh

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,17 @@ fi
9393
free_review_url="https://www.oasdiff.com/review?owner=${owner}&repo=${repo}&base_sha=$(urlencode "$free_base_sha")&rev_sha=${head_sha}&base_file=$(urlencode "$base_path")&rev_file=$(urlencode "$rev_path")"
9494
echo "::notice::📋 View API changes → ${free_review_url}"
9595

96-
# Build the JSON payload
97-
payload=$(jq -n \
96+
# Build the JSON payload. The `changes` array can be very large for
97+
# complex specs (one real-world report was observed at thousands of
98+
# entries running into the megabytes), so it's piped via stdin rather
99+
# than passed as a `--argjson` value. `--argjson` would put the entire
100+
# JSON string on jq's command line, exceeding the OS argument-length
101+
# limit (ARG_MAX, typically 128KB to 2MB depending on the kernel),
102+
# which surfaces as a confusing "jq: Argument list too long" error
103+
# that aborts the action right before the POST to oasdiff-service.
104+
# `printf` is a shell builtin in POSIX sh / busybox ash so its
105+
# arguments don't go through execve and aren't subject to ARG_MAX.
106+
payload=$(printf '%s' "$changes" | jq \
98107
--arg token "$github_token" \
99108
--arg owner "$owner" \
100109
--arg repo "$repo" \
@@ -104,8 +113,7 @@ payload=$(jq -n \
104113
--arg base_sha "$base_sha" \
105114
--arg base_file "$base" \
106115
--arg rev_file "$revision" \
107-
--argjson changes "$changes" \
108-
'{github: {token: $token, owner: $owner, repo: $repo, pull_number: $pr, head_sha: $sha, base_ref: $base_ref, base_sha: $base_sha}, base_file: $base_file, rev_file: $rev_file, changes: $changes}')
116+
'{github: {token: $token, owner: $owner, repo: $repo, pull_number: $pr, head_sha: $sha, base_ref: $base_ref, base_sha: $base_sha}, base_file: $base_file, rev_file: $rev_file, changes: .}')
109117

110118
# POST to oasdiff-service (requires token)
111119
if [ -z "$oasdiff_token" ]; then

0 commit comments

Comments
 (0)