# Batch Report **Batch**: 01 (cycle 5) **Tasks**: AZ-504 — Perf script: fix grep | wc -l pipefail crash in PT-08 **Date**: 2026-05-12 ## Task Results | Task | Status | Files Modified | Tests | AC Coverage | Issues | |------|--------|---------------|-------|-------------|--------| | AZ-504_perf_script_grep_pipefail_fix | Done (AC-1/AC-2 in-place verified; AC-3/AC-4 deferred to Step 15) | 1 file | pass (in-place harness, 4 cases under `set -e -o pipefail`) | 2/4 covered now; 2/4 deferred to Step 15 by spec design | None | ## Changes `scripts/run-performance-tests.sh:416-417` — replaced ```bash accepted=$(grep -o '"status":"accepted"' "$PERF_TMP_DIR/pt08_resp.json" | wc -l | tr -d ' ') rejected=$(grep -o '"status":"rejected"' "$PERF_TMP_DIR/pt08_resp.json" | wc -l | tr -d ' ') ``` with ```bash # AZ-504: grep exits 1 on zero matches. Under `set -o pipefail` (line 16) # that kills the assignment and crashes the script on the happy path # (rejected=0). Neutralise the no-match case locally with `|| true` so # the pipeline still produces a count. The response is compact JSON # (one line, all items) so `grep -o | wc -l` is required to count # occurrences — `grep -c` would only count matching lines (=1). The # file is guaranteed-readable here (curl wrote it earlier in this # iteration on the HTTP 200 branch), so grep cannot fail for I/O. accepted=$({ grep -o '"status":"accepted"' "$PERF_TMP_DIR/pt08_resp.json" || true; } | wc -l | tr -d ' ') rejected=$({ grep -o '"status":"rejected"' "$PERF_TMP_DIR/pt08_resp.json" || true; } | wc -l | tr -d ' ') ``` **Why `|| true` on grep instead of `grep -c`**: PT-08 response is compact JSON written by ASP.NET Core's default serializer — all items live on a single line of the response body. `grep -c` counts MATCHING LINES, so it would return 1 regardless of whether there are 1 or 10 accepted items (Risk 1 of the task spec). `grep -o` + `wc -l` preserves occurrence semantics. The `|| true` is the minimum local neutralisation of pipefail and is justified by the `coderule.mdc` exception "If an error is truly safe to ignore, log it or comment why" — the 8-line comment explains why grep cannot fail for I/O at this code path (file is curl-written, HTTP-200-gated). **Shellcheck pass over the file (Risk 2 of task spec)**: surveyed all `grep ... | wc -l` and `grep -[oc]` sites. Only two `grep -o ... | wc -l` sites exist in the script — both at lines 416-417 (the ones fixed). The third `grep -o` site (line 141, region status polling) is already protected by `head -1 || true` and is not vulnerable. No other defensive work required. ## AC Test Coverage | AC | Status | Where verified | |----|--------|----------------| | AC-1 — PT-08 completes on zero-rejected response | **Covered** | Standalone harness reproduces the exact pipeline under `set -e -o pipefail` with `accepted=2 rejected=0`; pipeline returns count `0` without script exit. | | AC-2 — PT-08 completes on zero-accepted response (defensive) | **Covered** | Same harness with `accepted=0 rejected=2`; pipeline returns count `0` for accepted without script exit. | | AC-3 — PT-08 summary line prints in full run | **Deferred to Step 15** | Requires `docker-compose up -d --build` + full default-parameter `./scripts/run-performance-tests.sh` (PERF_REPEAT_COUNT=20 PERF_UAV_BATCH_SIZE=10). This is exactly the Step 15 (Performance Test) gate for cycle 5. The spec is staged: AC-4 GIVEN clause depends on AC-3 + "the full perf run is green", so the natural verification environment is Step 15. | | AC-4 — Leftover deletion on green full run | **Deferred to Step 15** | By spec design — AC-4 says the leftover file is deleted "in the same commit" as the green full perf run. That commit IS the Step 15 deliverable. Confirmed in the perf-cycle3 leftover's "Replay attempt #4" entry. | **Spec-Gap?** No. AC-3 + AC-4 are not gaps for Step 10 — they are explicit Step 15 deliverables baked into the spec itself (AC-4 GIVEN clause). Step 10 (this batch) delivers the script fix that makes Step 15 possible. ## Test Strategy Note This project has no shell-script unit test infrastructure (no BATS, no `scripts/test_*.sh`). The established pattern is "the script is the test" — `scripts/run-tests.sh` and `scripts/run-performance-tests.sh` ARE the verification surface. Adding BATS for a 1-SP single-line fix would be infra creep explicitly disallowed by `coderule.mdc` ("Avoid boilerplate and unnecessary indirection ... follow established project patterns"). The standalone harness recorded above is repeatable on demand and produces evidence equivalent to a BATS smoke test. ## Code Review Verdict: PASS ## Auto-Fix Attempts: 0 ## Stuck Agents: None ## Next Batch: AZ-503 — Tile identity → UUIDv5 + integer UPSERT + bulk-list endpoint (3 SP)