Skip to content

fix(logger): route pino records to stderr so check --json stdout parses - #89

Draft
robotlearning123 wants to merge 3 commits into
mainfrom
loop/20260928_030031-issue79
Draft

robotlearning123 wants to merge 3 commits into
mainfrom
loop/20260928_030031-issue79

Conversation

@robotlearning123

Copy link
Copy Markdown
Member

Fixes #79

What

check --json emitted pino log records on stdout ahead of the JSON payload, so every programmatic consumer got unparseable output. The logger was constructed with no destination stream, so pino defaulted to fd 1 (stdout).

Change (2 commits, minimal diff):

  • src/utils/logger.ts: pass pino.destination(2) as the second argument to pino(...), routing all pino records to stderr. Level / base / transport behavior unchanged.
  • test/check-json.test.ts: new regression test that spawns check test/fixtures/minimal-repo --json, asserts stdout parses as JSON and the payload has areas.

Why

The CLI's only machine-readable interface was corrupt: stdout carried interleaved pino records ({"level":20,...}) before the payload, and JSON.parse on the captured stream failed with SyntaxError: Unexpected non-whitespace character after JSON at position 1312 (line 2 column 1).

Test evidence (run on this branch)

RED (test at 6e20459, before the logger fix):

not ok 1 - check --json
# pass 0
# fail 1   (SyntaxError from JSON.parse of stdout)

GREEN / acceptance oracle at HEAD:

$ npx tsx src/index.ts check test/fixtures/minimal-repo --json > /tmp/j.json 2>/tmp/j.err; echo $?
0
$ node -e "JSON.parse(require('fs').readFileSync('/tmp/j.json','utf8'));console.log('PARSE-OK')"
PARSE-OK

/tmp/j.json: pure payload (0 pino records), starts with {"ok": true, "data": {...}
/tmp/j.err: the 67 pino records, now on stderr

Full gates:

npm test       -> # tests 58 # pass 58 # fail 0
npm run check  -> typecheck + eslint + prettier all pass (1 pre-existing lint warning in test/e2e/cli.e2e.test.ts, untouched by this diff)

Diff scope (git diff --stat e704b49..HEAD): src/utils/logger.ts (+11/−8), test/check-json.test.ts (+27).

@robotlearning123

Copy link
Copy Markdown
Member Author

Independent verification pass (fresh worktrees from fetched origin refs) — review-pipeline status:

Verified by execution:

  • Narrow gate on HEAD (4002d78): npx tsx --test test/check-json.test.ts → # pass 1 / # fail 0.
  • Independent RED-first reproduction: the same test file run against origin/main (e704b49) → not ok 1 - check --json, # pass 0 / # fail 1 (JSON.parse of stdout fails pre-fix, passes post-fix). Premise of check --json emits unparseable JSON: pino writes log records to stdout #79 confirmed.
  • Diff scope confirmed minimal: src/utils/logger.ts (pino constructed with pino.destination(2), all records to stderr) + new test/check-json.test.ts regression test; no dependency or CI changes.

Pending gate: the independent cross-model code review could not run — the review lane returned 402 Payment Required (upstream balance exhausted) on 2/2 attempts. Per the quality gate, review must pass before this PR is merge-ready; it stays unmerged until the review completes and is re-verified.

Resume (one command once the review lane is healthy):
git -C /home/robot/workspace/agent-next/agent-ready worktree add /tmp/loop-wt-agent-next_agent-ready-issue79 loop/20260928_030031-issue79 && cd /tmp/loop-wt-agent-next_agent-ready-issue79 && npm ci --silent && timeout 540 grok --always-approve --cwd /tmp/loop-wt-agent-next_agent-ready-issue79 -p "REVIEW the diff origin/main...loop/20260928_030031-issue79 for correctness, test coverage, scope creep. Read the actual diff, reproduce blocking findings with commands. Output VERDICT: SHIP or VERDICT: FIX-FIRST, findings[] with file:line + failure scenario + severity, checked_does_not_hold[]."

@robotlearning123

Copy link
Copy Markdown
Member Author

Verification (cycle 20260928_030031, issue #79): premise reproduced on main — check . --json emitted 8 pino records on stdout before the payload; node JSON.parse failed at position 1214. Fix at head 4002d78 (src/utils/logger.ts, pino.destination(2)): acceptance oracle re-verified by execution in a fresh worktree — CLI exit 0, stdout is pure JSON payload (PARSE-OK, 3 areas), 67 log records now on stderr; new test/check-json.test.ts (spawn + JSON.parse + areas assert) 1 pass / 0 fail, RED proven at the pre-fix commit; full suite 58/58 and npm run check (typecheck+eslint+prettier) green per build verification. Independent review did NOT run (reviewer lane quota-blocked, retryAt 2026-09-29T05:10:50Z) — status: needs_work, review-gate only; zero code findings.

One-command resume when the reviewer lane recovers:
git -C /home/robot/workspace/agent-next/agent-ready fetch origin --prune && git -C /home/robot/workspace/agent-next/agent-ready worktree add /tmp/loop-wt-agent-next_agent-ready-issue79 loop/20260928_030031-issue79 2>/dev/null; timeout 540 grok --always-approve --cwd /tmp/loop-wt-agent-next_agent-ready-issue79 -p "REVIEW origin/main...loop/20260928_030031-issue79 on agent-next/agent-ready for correctness, test coverage, and scope creep. Read the actual diff (git diff). Judge against issue #79. Output exactly: VERDICT: SHIP or VERDICT: FIX-FIRST, then findings[] each with file:line + concrete failure scenario + severity, then checked_does_not_hold[]."

@robotlearning123

Copy link
Copy Markdown
Member Author

Reviewer lane recovered — grok review result: VERDICT FIX-FIRST. action.yml:64 (major, reproduced): RESULT=$(node dist/index.js check "$SCAN_PATH" --json $STRICT_FLAG 2>&1) merges pino stderr back into the JSON result output — merged stream starts with a pino line and throws SyntaxError: Unexpected non-whitespace character after JSON at position 1312 (pino.destination(2) in src/utils/logger.ts:22 does not fix this consumer). test/check-json.test.ts:24 (minor): new test only JSON.parses spawnSync stdout, so npm test stays green (58/58) while the action capture still fails — does not lock the issue's action consumer.

@robotlearning123

Copy link
Copy Markdown
Member Author

R2 repair verification — independent, by execution

Verified at origin/loop/20260928_030031-issue79 = a5bb15b in a fresh detached worktree; rebuilt via npm ci --no-audit --no-fund && npm run build (exit 0).

a5bb15b is already on this branch and is exactly the one-line removal of 2>&1 from the action.yml:64 capture (action.yml | 2 +-). No duplicate commit added.

RED (pre-fix line temporarily restored, uncommitted) — finding reproduced:

$ RESULT=$(node dist/index.js check "$PWD" --json 2>&1); \
  printf '%s' "$RESULT" | node -e '...JSON.parse(d)...'
PARSE FAIL: Unexpected non-whitespace character after JSON at position 1043 (line 2 column 1)   # exit 1

merged RESULT begins with a pino record: {"level":20,...,"msg":"File does not exist or is not readable"}

GREEN (committed fix, stderr goes to the action log):

$ RESULT=$(node dist/index.js check "$PWD" --json 2>/dev/null); \
  printf '%s' "$RESULT" | node -e '...JSON.parse(d)...'
PARSE OK   # exit 0

Split confirmed on the built CLI: stderr = 8235 bytes of pino records; stdout = pure JSON.

Downstream handling: $GITHUB_OUTPUT write via the RESULT_EOF heredoc simulated with the clean RESULT — value block re-parses as JSON (1713 bytes). The passed output uses EXIT_CODE only; nothing parses error text out of merged stderr.

Issue oracle: node dist/index.js check . --json > /tmp/j.json && node -e 'JSON.parse(...)' → exit 0 (1714 bytes pure JSON).
grep -n '2>&1' action.yml → no matches.
npm test (tsx --test test/*.test.ts): # tests 58 / # pass 58 / # fail 0, including test/check-json.test.ts → ok 1 - emits parseable JSON on stdout only.

Scope: PR diff is action.yml (2 +-), src/utils/logger.ts (19), test/check-json.test.ts (27 new) — no .github/workflows/ or dependency changes. Base (main = e704b49) still carries 2>&1 at action.yml:64; this PR fixes it.

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.

check --json emits unparseable JSON: pino writes log records to stdout

1 participant