|
| 1 | +# Case Study: Component Sizes Not Calculated or Pushed to README on Push to Main |
| 2 | + |
| 3 | +**Issue:** [#35 - Components sizes are not calculated or pushed to README.md on push to main branch](https://github.com/link-foundation/sandbox/issues/35) |
| 4 | + |
| 5 | +**Date of Investigation:** 2026-01-31 |
| 6 | + |
| 7 | +## Executive Summary |
| 8 | + |
| 9 | +The `measure-disk-space.yml` workflow has never successfully measured all component disk sizes and pushed the results to `README.md`. Two root causes were identified: (1) the workflow's path-based trigger did not include the measurement scripts themselves, so fixes to those scripts never re-triggered the workflow, and (2) the `sed`-based JSON manipulation in the measurement script was fragile and failed on component names containing special characters (e.g., `C/C++ Tools`). Additionally, a pipeline masking issue (`| tee`) hid script failures from the workflow, allowing it to continue with incomplete data. |
| 10 | + |
| 11 | +## Timeline of Events |
| 12 | + |
| 13 | +| Time (UTC) | Event | Details | |
| 14 | +|------------|-------|---------| |
| 15 | +| 2026-01-29 14:07:09 | First workflow run (035998b) | Succeeded superficially, but only recorded 1 component (0MB) due to broken apt (issue-29). No validation existed yet, so 0MB data was committed to main. | |
| 16 | +| 2026-01-29 ~14:09 | 0MB data committed | `chore: update component disk space measurements (0MB total)` pushed to main (commit 3d75e41) | |
| 17 | +| 2026-01-29 ~14:35 | Issue-29 fix merged | PR #30 fixes apt cleanup, adds validation step (commit a646fe6) | |
| 18 | +| 2026-01-29 18:21:09 | Second workflow run (a646fe6) | Triggered by issue-29 fix merge. **Failed** with sed error on "C/C++ Tools" component. Only 2 components measured. Validation correctly rejected. | |
| 19 | +| 2026-01-29 18:39-18:48 | Issue-31 fix developed | PR #32 changes sed delimiter from `/` to `\|` | |
| 20 | +| 2026-01-29 ~18:48 | Issue-31 fix merged (52cbffc) | sed delimiter fixed, but this only changed `scripts/measure-disk-space.sh` | |
| 21 | +| 2026-01-29 18:48+ | **No workflow re-trigger** | The workflow path filter only watches `scripts/ubuntu-24-server-install.sh`, `Dockerfile`, and the workflow file itself — NOT `scripts/measure-disk-space.sh` | |
| 22 | +| 2026-01-31 | Issue #35 opened | Component sizes still show 0MB in README | |
| 23 | + |
| 24 | +## Root Cause Analysis |
| 25 | + |
| 26 | +### Root Cause 1: Incomplete Workflow Path Triggers |
| 27 | + |
| 28 | +The `measure-disk-space.yml` workflow was configured to trigger only on changes to: |
| 29 | +```yaml |
| 30 | +paths: |
| 31 | + - 'scripts/ubuntu-24-server-install.sh' |
| 32 | + - 'Dockerfile' |
| 33 | + - '.github/workflows/measure-disk-space.yml' |
| 34 | +``` |
| 35 | +
|
| 36 | +Missing from this list: |
| 37 | +- `scripts/measure-disk-space.sh` — the main measurement script |
| 38 | +- `scripts/update-readme-sizes.sh` — the README updater script |
| 39 | + |
| 40 | +This meant that fixing the measurement script (issue-31, commit 52cbffc) did **not** trigger a re-run of the measurement workflow. The fixed code was never executed. |
| 41 | + |
| 42 | +### Root Cause 2: Fragile sed-Based JSON Manipulation |
| 43 | + |
| 44 | +The `add_measurement()` function used `sed` to manipulate JSON, which is inherently fragile: |
| 45 | + |
| 46 | +```bash |
| 47 | +# Even with | delimiter (after issue-31 fix), sed is fragile for JSON: |
| 48 | +current_json=$(echo "$current_json" | sed "s|\"components\": \[\]|\"components\": [$new_component]|") |
| 49 | +current_json=$(echo "$current_json" | sed "s|\]$|,$new_component]|") |
| 50 | +``` |
| 51 | + |
| 52 | +While the issue-31 fix changed the delimiter from `/` to `|`, this approach remains vulnerable to: |
| 53 | +- Any future component name containing `|` |
| 54 | +- Regex metacharacters in component values |
| 55 | +- Multi-line JSON formatting changes |
| 56 | +- Shell quoting edge cases with special characters |
| 57 | + |
| 58 | +### Root Cause 3: Pipeline Masking Script Failures |
| 59 | + |
| 60 | +The workflow ran the measurement script through a pipe: |
| 61 | +```yaml |
| 62 | +sudo ./scripts/measure-disk-space.sh ... 2>&1 | tee measurement.log |
| 63 | +``` |
| 64 | + |
| 65 | +Without `set -o pipefail` in the workflow step, bash reports the exit code of the **last** command in the pipeline (`tee`, which always succeeds), not the measurement script. When the script crashed due to the sed error, the workflow continued as if nothing happened, producing incomplete JSON data. |
| 66 | + |
| 67 | +### How the Three Root Causes Interacted |
| 68 | + |
| 69 | +``` |
| 70 | +Issue-29 fix (a646fe6) merged to main |
| 71 | + │ |
| 72 | + ▼ |
| 73 | +measure-disk-space workflow triggered (correct — install script changed) |
| 74 | + │ |
| 75 | + ▼ |
| 76 | +Script crashes on "C/C++ Tools" due to sed / delimiter (issue-31) |
| 77 | + │ │ |
| 78 | + ▼ ▼ |
| 79 | +Pipeline masks failure Only 2 components in JSON |
| 80 | +(tee exit code 0) (total_size_mb: 0) |
| 81 | + │ │ |
| 82 | + ▼ ▼ |
| 83 | +Workflow continues Validation catches incomplete data |
| 84 | + │ |
| 85 | + ▼ |
| 86 | + Workflow fails (correct behavior) |
| 87 | +``` |
| 88 | +
|
| 89 | +``` |
| 90 | +Issue-31 fix (52cbffc) merged to main |
| 91 | + │ |
| 92 | + ▼ |
| 93 | +measure-disk-space workflow NOT triggered |
| 94 | +(scripts/measure-disk-space.sh not in path triggers) |
| 95 | + │ |
| 96 | + ▼ |
| 97 | +README still shows 0MB — issue #35 opened |
| 98 | +``` |
| 99 | +
|
| 100 | +## Solution |
| 101 | +
|
| 102 | +### Fix 1: Add Measurement Scripts to Workflow Path Triggers |
| 103 | +
|
| 104 | +```yaml |
| 105 | +paths: |
| 106 | + - 'scripts/ubuntu-24-server-install.sh' |
| 107 | + - 'scripts/measure-disk-space.sh' # NEW |
| 108 | + - 'scripts/update-readme-sizes.sh' # NEW |
| 109 | + - 'Dockerfile' |
| 110 | + - '.github/workflows/measure-disk-space.yml' |
| 111 | +``` |
| 112 | + |
| 113 | +This ensures any changes to measurement-related scripts will trigger a re-run. |
| 114 | + |
| 115 | +### Fix 2: Add pipefail to Workflow Measurement Step |
| 116 | + |
| 117 | +```yaml |
| 118 | +run: | |
| 119 | + set -o pipefail |
| 120 | + # ... measurement commands ... |
| 121 | + sudo ./scripts/measure-disk-space.sh ... 2>&1 | tee measurement.log |
| 122 | +``` |
| 123 | +
|
| 124 | +This ensures script failures propagate through the `tee` pipeline and are detected by the workflow. |
| 125 | + |
| 126 | +### Fix 3: Replace sed-Based JSON Manipulation with Python |
| 127 | + |
| 128 | +Instead of using sed (which is fragile for structured data), use Python's `json` module: |
| 129 | + |
| 130 | +**Before (fragile sed):** |
| 131 | +```bash |
| 132 | +current_json=$(echo "$current_json" | sed "s|\"components\": \[\]|\"components\": [$new_component]|") |
| 133 | +``` |
| 134 | + |
| 135 | +**After (robust Python):** |
| 136 | +```bash |
| 137 | +python3 -c " |
| 138 | +import json, sys |
| 139 | +with open('$JSON_OUTPUT_FILE', 'r') as f: |
| 140 | + data = json.load(f) |
| 141 | +data['components'].append({ |
| 142 | + 'name': sys.argv[1], |
| 143 | + 'category': sys.argv[2], |
| 144 | + 'size_bytes': int(sys.argv[3]), |
| 145 | + 'size_mb': int(sys.argv[4]) |
| 146 | +}) |
| 147 | +with open('$JSON_OUTPUT_FILE', 'w') as f: |
| 148 | + json.dump(data, f) |
| 149 | +" "$name" "$category" "$size_bytes" "$size_mb" |
| 150 | +``` |
| 151 | + |
| 152 | +Python's `json` module handles all special characters correctly and produces valid JSON output regardless of component names. |
| 153 | + |
| 154 | +## Evidence |
| 155 | + |
| 156 | +### Failed Run Logs (Run 21489818730) |
| 157 | + |
| 158 | +The sed error at the C/C++ Tools component: |
| 159 | +``` |
| 160 | +[✓] Recorded: .NET SDK 8.0 - 481MB |
| 161 | +[*] Measuring installation: C/C++ Tools (CMake, Clang, LLVM, LLD) |
| 162 | +... |
| 163 | +sed: -e expression #1, char 20: unknown option to `s' |
| 164 | +=== Measurement complete === |
| 165 | +``` |
| 166 | + |
| 167 | +Validation failure: |
| 168 | +``` |
| 169 | +Total size: 0MB |
| 170 | +Component count: 2 |
| 171 | +WARNING: Measurements appear incomplete or invalid! |
| 172 | + - Total size: 0MB (expected >= 1000MB) |
| 173 | + - Components: 2 (expected >= 10) |
| 174 | +``` |
| 175 | + |
| 176 | +### "Successful" Run Logs (Run 21481304786) |
| 177 | + |
| 178 | +The earlier run appeared successful but actually failed silently: |
| 179 | +``` |
| 180 | +E: Package 'build-essential' has no installation candidate |
| 181 | +E: Unable to locate package expect |
| 182 | +[!] Installation of Essential Tools failed |
| 183 | +[✓] Recorded: Essential Tools - 0MB |
| 184 | +=== Measurement complete === |
| 185 | +``` |
| 186 | + |
| 187 | +This run had no validation step, so it committed 0MB data to main. |
| 188 | + |
| 189 | +## Files Modified |
| 190 | + |
| 191 | +- `.github/workflows/measure-disk-space.yml` — Added measurement scripts to path triggers; added `set -o pipefail` |
| 192 | +- `scripts/measure-disk-space.sh` — Replaced sed-based JSON manipulation with Python |
| 193 | + |
| 194 | +## Prevention |
| 195 | + |
| 196 | +1. **Include all related scripts in workflow triggers**: When a workflow depends on scripts, ensure those scripts are listed in the `paths` filter |
| 197 | +2. **Use language-appropriate tools for data manipulation**: Use Python/jq for JSON, not sed/awk |
| 198 | +3. **Enable pipefail in CI steps**: Always use `set -o pipefail` when piping command output through `tee` or other tools |
| 199 | +4. **Test script changes trigger workflows**: Verify path filters match all relevant files |
| 200 | + |
| 201 | +## Related Issues |
| 202 | + |
| 203 | +- [#29 - Components size update failed](https://github.com/link-foundation/sandbox/issues/29) — APT cleanup breaking package installation (fixed) |
| 204 | +- [#31 - CI/CD failed](https://github.com/link-foundation/sandbox/issues/31) — sed delimiter error with `/` in component names (partially fixed, this issue completes the fix) |
| 205 | + |
| 206 | +## CI Logs |
| 207 | + |
| 208 | +Full CI logs are preserved in: |
| 209 | +- `ci-logs/measure-disk-space-failed-21489818730.log` — Failed run with sed error |
| 210 | +- `ci-logs/measure-disk-space-success-21481304786.log` — Earlier run with broken apt |
| 211 | + |
| 212 | +Online: |
| 213 | +- [GitHub Actions Run 21489818730](https://github.com/link-foundation/sandbox/actions/runs/21489818730) — Failed measurement run |
| 214 | +- [GitHub Actions Run 21481304786](https://github.com/link-foundation/sandbox/actions/runs/21481304786) — Earlier "successful" run with 0MB data |
| 215 | + |
| 216 | +## Conclusion |
| 217 | + |
| 218 | +This issue was caused by a combination of three problems: incomplete workflow path triggers, fragile sed-based JSON manipulation, and pipeline error masking. The issue-31 fix addressed the immediate sed delimiter problem but was never re-executed because the workflow path triggers didn't include the measurement script. The comprehensive fix adds the missing path triggers, replaces sed with Python for JSON manipulation (eliminating the entire class of special-character bugs), and adds `pipefail` to detect script failures properly. Once merged, the workflow will trigger and should produce accurate component size measurements. |
0 commit comments