Skip to content

tools(budget): daily-cost-report.sh wrapper — visibility surface scaffold for task #287#611

Merged
AceHack merged 1 commit intomainfrom
tools/budget-daily-cost-report
Apr 26, 2026
Merged

tools(budget): daily-cost-report.sh wrapper — visibility surface scaffold for task #287#611
AceHack merged 1 commit intomainfrom
tools/budget-daily-cost-report

Conversation

@AceHack
Copy link
Copy Markdown
Member

@AceHack AceHack commented Apr 26, 2026

Summary

Adds tools/budget/daily-cost-report.sh — wrapper that chains snapshot-burn.sh + project-runway.sh and writes docs/budget-history/latest-report.md so the human maintainer can cat ONE file to see runway state.

Direct response to maintainer 2026-04-26: "we need to get that resource/costs monitoring done in the next few days ... so we can see the costs". Task #287 sub-step 1.

What this PR does NOT do

Test plan

  • bash -n passes
  • Per Otto-348 verify-substrate-exists: confirmed no existing wrapper before drafting (ls tools/budget/daily-cost-report.sh tools/budget/cost-monitor.sh tools/budget/refresh-report.sh — all absent)
  • --skip-snapshot smoke test produces valid markdown report
  • CI shellcheck on the new script

Composes with

…urface (task #287)

The human maintainer 2026-04-26: "we need to get that resource/costs
monitoring done in the next few days ... so we can see the costs"

The two existing budget primitives (snapshot-burn.sh + project-runway.sh)
require manual orchestration to produce a glanceable surface. This wrapper
chains them and writes docs/budget-history/latest-report.md so the
maintainer can `cat` ONE file to see runway state.

## What this commits

- New file `tools/budget/daily-cost-report.sh` (~115 lines, exec-bit set,
  bash 3.2-portable per the same discipline as snapshot-burn.sh)
- Three flags: default (full run), `--dry-run` (passes to snapshot-burn,
  still writes report), `--skip-snapshot` (regenerates report from
  existing snapshots only — useful for testing + bootstrap)
- Writes `docs/budget-history/latest-report.md` (OVERWRITES, not append;
  history lives in snapshots.jsonl as append-only)
- Bootstrap path when snapshots.jsonl doesn't exist yet (writes a
  placeholder report explaining the N >= 2 prerequisite)

## What this does NOT commit

- The /schedule routine that runs this daily (per Otto-275 log-don't-implement
  + agent-autonomy-boundary; awaits explicit human-maintainer confirmation)
- Capture of the GitHub `Copilot over budget` signal the maintainer
  surfaced 2026-04-26 (LFG: $1.90 / $0 budget). The current
  `gh api /orgs/<org>/copilot/billing` endpoint returns seat info but
  not the spend-vs-budget signal — separate follow-up data-fetch work
- Direct-to-main commit of the daily report (gated on task #276 + B-0032)
- Slack / PR-comment alerting on EXCEEDS conditions

## Verification

- `bash -n` passes
- `--skip-snapshot` smoke test wrote a valid report file (then deleted
  pre-commit; the wrapper is the substrate, not the report itself)
- shellcheck clean (per the discipline; CI runs the lint)
- Per Otto-348 verify-substrate-exists: confirmed no existing wrapper
  (`ls tools/budget/daily-cost-report.sh tools/budget/cost-monitor.sh
  tools/budget/refresh-report.sh` — all absent) BEFORE drafting

Composes with: task #287 (this is sub-step 1 of the visibility-surface
deliverable), tools/budget/snapshot-burn.sh, tools/budget/project-runway.sh,
docs/budget-history/README.md, GOVERNANCE.md (no changes needed; this is
factory-internal tooling).
@AceHack AceHack enabled auto-merge (squash) April 26, 2026 13:52
Copilot AI review requested due to automatic review settings April 26, 2026 13:52
@AceHack AceHack merged commit 744e268 into main Apr 26, 2026
22 checks passed
@AceHack AceHack deleted the tools/budget-daily-cost-report branch April 26, 2026 13:54
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b5a532264e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
git_sha="$(git -C "$repo_root" rev-parse HEAD 2>/dev/null || echo unknown)"

cat > "$report_path" <<EOF
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Check report write succeeds

The script always prints OK: daily cost report regenerated even if writing latest-report.md fails, because cat > "$report_path" <<EOF is not checked and set -e is not enabled. In environments with a read-only checkout, missing parent directory, or disk-full condition, this produces a false-success signal for the daily monitoring job while leaving the report stale or absent.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a single entry-point wrapper script to generate a daily, human-readable cost/runway report by chaining the existing snapshot + projection primitives and writing the result to docs/budget-history/latest-report.md.

Changes:

  • Introduces tools/budget/daily-cost-report.sh wrapper with --dry-run / --skip-snapshot options.
  • Runs snapshot-burn.sh (optional) then project-runway.sh, and writes an overwrite-in-place markdown report containing the projection text plus metadata.

Comment on lines +43 to +72
snapshot_args=""
skip_snapshot="false"

while [ $# -gt 0 ]; do
case "$1" in
--dry-run) snapshot_args="--dry-run"; shift ;;
--skip-snapshot) skip_snapshot="true"; shift ;;
-h|--help)
sed -n '2,30p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
*)
echo "error: unknown argument '$1'" >&2
exit 2 ;;
esac
done

script_dir="$(cd "$(dirname "$0")" && pwd)"
repo_root="$(cd "$script_dir/../.." && pwd)"
report_path="$repo_root/docs/budget-history/latest-report.md"
snapshots_path="$repo_root/docs/budget-history/snapshots.jsonl"

# Step 1 — capture snapshot (unless skipped)
if [ "$skip_snapshot" = "false" ]; then
echo "==> snapshot-burn.sh $snapshot_args"
if [ -n "$snapshot_args" ]; then
"$script_dir/snapshot-burn.sh" $snapshot_args || {
echo "error: snapshot-burn.sh failed (exit $?)" >&2
exit 1
}
else
"$script_dir/snapshot-burn.sh" || {
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Avoid passing optional flags via an unquoted string ($snapshot_args). Even though it’s currently only --dry-run, this pattern triggers ShellCheck (SC2086) and is brittle if more args are added later. Prefer an array (e.g., snapshot_args=() + snapshot_args+=(--dry-run)), and invoke snapshot-burn with "$script_dir/snapshot-burn.sh" "${snapshot_args[@]}" (or no array when empty).

Copilot uses AI. Check for mistakes.
else
echo "==> project-runway.sh"
projection="$("$script_dir/project-runway.sh" 2>&1)" || {
echo "error: project-runway.sh failed (exit $?)" >&2
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: projection="$($script_dir/project-runway.sh 2>&1)" || { ... } captures stderr into projection, but on failure the captured output is never printed, so the user loses the actual error details. Consider restructuring to capture output, and if the command fails, echo the captured output to stderr before exiting (or only capture stdout and let stderr flow through).

Suggested change
echo "error: project-runway.sh failed (exit $?)" >&2
status=$?
if [ -n "$projection" ]; then
printf '%s\n' "$projection" >&2
fi
echo "error: project-runway.sh failed (exit $status)" >&2

Copilot uses AI. Check for mistakes.
Comment on lines +97 to +137
cat > "$report_path" <<EOF
# Latest cost projection — auto-generated

**Generated:** \`$ts\`
**Factory git SHA:** \`$git_sha\`
**Source:** \`tools/budget/daily-cost-report.sh\` (wraps snapshot-burn.sh + project-runway.sh)

This file is **OVERWRITTEN** on each daily run. Historical snapshots live in
\`docs/budget-history/snapshots.jsonl\` (append-only); historical projections
can be reconstructed from any snapshot subset via \`tools/budget/project-runway.sh\`.

---

## Projection text

\`\`\`text
$projection
\`\`\`

---

## How to read this

- **\`Actions billable_ms cumulative\`** — cumulative GitHub-Actions billable runtime across captured snapshots. On public repos this is typically 0 (included minutes); meaningful for macOS / private-repo / Enterprise-plan accounts.
- **\`Per-PR Actions ms (naive)\`** — rolling-window estimate of per-merged-PR Actions cost. Caveats in the projection text below; treat as proxy until \`N \\geq 3\` cumulative snapshots exist.
- **\`Actions fit\`** — whether projected Stages 1-4 burn fits the configured free-tier allowance. If \`EXCEEDS\`, the gate-conditions section names escape valves.
- **\`Copilot projected USD\`** — assumed-30-day span at the current seat count and rate. Re-run with \`--copilot-rate\` to model rate changes.

---

## Source data

- Snapshots: \`docs/budget-history/snapshots.jsonl\`
- Methodology: \`docs/budget-history/README.md\`
- Wrapper: \`tools/budget/daily-cost-report.sh\` (this run)
- Capture script: \`tools/budget/snapshot-burn.sh\`
- Projection script: \`tools/budget/project-runway.sh\`

EOF

echo "==> wrote $report_path"
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: Report generation writes directly to latest-report.md via a heredoc. If the write fails (permissions/disk full) or the process is interrupted mid-write, the file can be left missing/truncated, and because set -e is not enabled this failure won’t necessarily stop the script. Write to a temp file and mv it into place (atomic on same filesystem), and ensure the write/rename failures are checked so the script exits non-zero when the report can’t be updated.

Copilot uses AI. Check for mistakes.
- **\`Actions billable_ms cumulative\`** — cumulative GitHub-Actions billable runtime across captured snapshots. On public repos this is typically 0 (included minutes); meaningful for macOS / private-repo / Enterprise-plan accounts.
- **\`Per-PR Actions ms (naive)\`** — rolling-window estimate of per-merged-PR Actions cost. Caveats in the projection text below; treat as proxy until \`N \\geq 3\` cumulative snapshots exist.
- **\`Actions fit\`** — whether projected Stages 1-4 burn fits the configured free-tier allowance. If \`EXCEEDS\`, the gate-conditions section names escape valves.
- **\`Copilot projected USD\`** — assumed-30-day span at the current seat count and rate. Re-run with \`--copilot-rate\` to model rate changes.
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: In the generated report, the bullet “Re-run with --copilot-rate to model rate changes” is ambiguous about which command to rerun (this wrapper doesn’t accept that flag). Consider spelling out tools/budget/project-runway.sh --copilot-rate ... (or documenting that the wrapper forwards flags, if you add forwarding later) to avoid user confusion.

Suggested change
- **\`Copilot projected USD\`** — assumed-30-day span at the current seat count and rate. Re-run with \`--copilot-rate\` to model rate changes.
- **\`Copilot projected USD\`** — assumed-30-day span at the current seat count and rate. Re-run \`tools/budget/project-runway.sh --copilot-rate ...\` to model rate changes.

Copilot uses AI. Check for mistakes.
AceHack added a commit that referenced this pull request Apr 26, 2026
…#287) (#615)

* budget: capture first cost snapshot + bootstrap latest-report.md (task #287 sub-step 2 partial)

Ran tools/budget/daily-cost-report.sh on main (just landed via PR #611) to
bootstrap the first snapshot in docs/budget-history/snapshots.jsonl + the
glanceable latest-report.md.

## What this snapshot captures (LFG, 2026-04-26T13:57:01Z)

- Copilot: Business plan, 1 active seat, $19/month single-span projection
- Zeta repo: 20 last-runs / 513s total duration / 0 billable_ms (public-repo
  included minutes) / 5 recent merged PRs
- N=1 — projection is "insufficient data" per the script's honest reporting;
  needs N>=3 across >=2 LFG merges before decision-ready

## What this gives the maintainer

`cat docs/budget-history/latest-report.md` → see costs in <2 seconds.
Replaces manual GitHub UI checking (the failure mode Aaron surfaced 2026-04-26
with the LFG Copilot $1.90/$0 over-budget alert + the $3.80 actual seat-rate
clarification). The report's "Projection parameters" section makes the
$19/month single-seat assumption visible alongside the spend.

## Why N=1 is fine to commit now

Each future daily run (when scheduled) appends another snapshot row to
snapshots.jsonl AND overwrites latest-report.md. The N>=3 projection threshold
becomes meaningful with snapshot accumulation; the bootstrap-with-N=1 here
seeds the time-series.

Per Otto-275 log-don't-implement: NOT scheduling the daily routine in this
PR — that's task #287 sub-step 2 (full) pending Aaron's /schedule confirmation.
This commit is the manual one-shot to seed visibility today.

Composes with task #287, PR #611 (the wrapper), tools/budget/snapshot-burn.sh,
tools/budget/project-runway.sh, docs/budget-history/README.md.

* fix(budget): MD012 trailing blank line in latest-report.md + heredoc template

CI markdownlint flagged docs/budget-history/latest-report.md:84 with MD012 multiple-consecutive-blanks. Root cause was the heredoc template in tools/budget/daily-cost-report.sh having a blank line before EOF, which produced \n\n termination on every regenerated report. Fix removes the blank line in the heredoc and strips the trailing blank from the materialized file. Single-trailing-newline convention restored.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(budget): strip absolute path from latest-report.md evidence-source

Copilot review on PR #615 flagged P1 — the auto-generated latest-report.md was emitting an absolute filesystem path (`/Users/acehack/Documents/src/repos/Zeta/docs/budget-history/snapshots.jsonl`) leaking the generator's machine/username and breaking reproducibility for other clones.

Fix: strip the repo-root prefix in tools/budget/project-runway.sh emit using bash parameter expansion (`${file#"$repo_root"/}`). The displayed evidence path is now repo-relative (`docs/budget-history/snapshots.jsonl`). When users override via --file with an external path, the absolute path is preserved (correct — they're naming a file outside the repo).

Regenerated latest-report.md to apply the fix to the materialized report.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
AceHack added a commit that referenced this pull request Apr 26, 2026
…des #618 (#620)

PR #618 (consolidated-backfill 5 rows 13:33Z..13:58Z) became DIRTY when #617 (14:10Z row) merged to main, because parallel PRs (#604/#609/#611/#613) had already landed 2 of #618's 5 rows on main (13:41Z + 13:45Z + 13:48Z), leaving #618's commit duplicating already-merged content.

Per Otto-2026-04-26 drain-chronologically + Otto-220 don't-lose-substrate: extract just the 3 rows missing from main (verified via grep) and apply chronologically using tools/hygiene/sort-tick-history-canonical.py. The clean-reapply pattern (used earlier this session for #619 Otto-344 recovery) avoids both rebase-conflict resolution AND substrate-loss.

3 rows added:
- 13:33:08Z — parallel-tick-history-DIRTY cleanup tick
- 13:55:19Z — sibling-DIRTY consolidated-backfill #613 tick
- 13:58:22Z — first cost snapshot captured / latest-report.md bootstrapped

Tick-order check: 151 rows non-decreasing OK. markdownlint OK.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
AceHack added a commit that referenced this pull request Apr 26, 2026
#620 supersession (#625)

Otto-347 2nd-agent verification (independent subagent audit) caught substrate loss when I closed #618 as 'superseded by #620': I had hallucinated #618's actual row contents. #618 carried 13:33+13:38+13:52+13:55+13:58Z; #620 captured only 13:33+13:55+13:58Z. The 13:38 and 13:52 rows were never on main.

Both rows extracted verbatim from preserved branches via 'git show <branch>:<path>' per Otto-238 retractability:
- 13:38:50Z (~2834 bytes): tick documenting Otto-348 origin material — the verify-substrate-exists discovery (tools/hygiene/append-tick-history-row.sh already existed); direct-to-main-tick-history is the actual substrate gap (task #276)
- 13:52:34Z (~3043 bytes): tick documenting task #287 sub-step 1 ship (PR #611 daily-cost-report wrapper) + LFG Copilot OVER BUDGET signal absorbed + agent-autonomy boundary on Copilot stop-usage decision

Source branches retained on origin per Otto-238: tick-history/2026-04-26T13-39Z (PR #607) and tick-history/2026-04-26T13-53Z (PR #612).

This is the fourth+1th use of the clean-reapply pattern this session — but importantly, the FIRST one triggered by 2nd-agent verification finding loss the same-agent verification missed. Direct empirical evidence Otto-347 is load-bearing AS WRITTEN ('would be good to ask another cli'), not just as same-agent diff.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
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.

2 participants