From d8b06c2ba326ddd81dae271036cacdaea303bb02 Mon Sep 17 00:00:00 2001 From: tend-agent <270458913+tend-agent@users.noreply.github.com> Date: Mon, 10 Aug 2026 08:33:59 +0000 Subject: [PATCH 1/5] fix(review-runs): anchor the census window on the predecessor run, not now-24h Closes #938 --- .../skills/review-runs/SKILL.md | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/plugins/tend-ci-runner/skills/review-runs/SKILL.md b/plugins/tend-ci-runner/skills/review-runs/SKILL.md index aebcb044..cc0a155f 100644 --- a/plugins/tend-ci-runner/skills/review-runs/SKILL.md +++ b/plugins/tend-ci-runner/skills/review-runs/SKILL.md @@ -126,11 +126,32 @@ Never replace the body — prior entries contain per-run evidence needed for gat ## Step 1: Find recent runs -List tend CI runs that completed in the past 24 hours (the cron runs daily): +List tend CI runs that completed since the previous `review-runs` run (nominally 24 hours — the cron runs daily): ```bash REPO=$(gh repo view --json nameWithOwner --jq '.nameWithOwner') -SINCE=$(date -u -d '24 hours ago' +%Y-%m-%dT%H:%M:%SZ) +# Anchor the window on the predecessor's start, not on a duration back from +# `now`. A `date -u -d '24 hours ago'` resolves when the *agent* runs it — the +# run's start plus container boot and skill loading — so it opens strictly after +# the predecessor started and drops every run in that band, which is exactly +# the work the predecessor triggered. Anchoring makes consecutive windows tile, +# and every later step reads the same `$SINCE` instead of recomputing a +# duration that has drifted further by then. +# +# Derive the workflow id from this run rather than assuming the file name; +# exclude this run, because a re-run attempt of it can already read as +# completed and anchoring on itself collapses the window to zero. +# `status=success` reaches past a predecessor that died before its census, so +# that band still gets covered. Clamp a stale or missing anchor (an outage, or +# a fresh repo with no predecessor) so the window can recover one skipped day +# without pulling in a week — Step 5 dedups whatever a widened window sees +# twice. +WF_ID=$(gh api "repos/$REPO/actions/runs/$GITHUB_RUN_ID" --jq '.workflow_id') +PREV_START=$(gh api "repos/$REPO/actions/workflows/$WF_ID/runs?status=success&per_page=10" \ + --jq "[.workflow_runs[] | select(.id != ${GITHUB_RUN_ID:-0}) | .created_at] | max // empty") +SINCE=${PREV_START:-$(date -u -d '25 hours ago' +%Y-%m-%dT%H:%M:%SZ)} +FLOOR=$(date -u -d '49 hours ago' +%Y-%m-%dT%H:%M:%SZ) +if [[ "$SINCE" < "$FLOOR" ]]; then SINCE=$FLOOR; fi # Add the repo's extra prefixes from its `running-tend` skill: any workflow # running the tend action is in scope, not just the generated `tend-*` ones. # Step 2 prices the same list. @@ -175,10 +196,14 @@ After retrieving the timeout cap from the workflow file, flag any job whose dura Run the token report script to get per-run token counts: ```bash -"${CLAUDE_PLUGIN_ROOT}/scripts/token-report.sh" 24 > /tmp/token-report.json +# Whole hours back to Step 1's anchor, rounded up so the whole band is priced. +# A literal `24` here reopens the same gap Step 1 closed, and clips a wider band +# than Step 1 did because `now` moved on during the session. +HOURS=$(( ( $(date -u +%s) - $(date -u -d "$SINCE" +%s) + 3599 ) / 3600 )) +"${CLAUDE_PLUGIN_ROOT}/scripts/token-report.sh" "$HOURS" > /tmp/token-report.json ``` -Pass the same extra prefixes Step 1 censuses, so the two steps agree on what the fleet is — the repo's `running-tend` skill is the source for both (e.g. `review-` for a `review-reviewers` workflow that uses the tend action but isn't named `tend-*`). +Pass the same extra prefixes Step 1 censuses (after `$HOURS`, which the script reads as its first positional arg), so the two steps agree on what the fleet is — the repo's `running-tend` skill is the source for both (e.g. `review-` for a `review-reviewers` workflow that uses the tend action but isn't named `tend-*`). Include the totals and per-workflow breakdown in the summary (Step 7). Flag any runs with unusually high token usage for closer inspection in Step 3. From 99ec210de675028e03b277da2fd16a4343f566a6 Mon Sep 17 00:00:00 2001 From: tend-agent <270458913+tend-agent@users.noreply.github.com> Date: Mon, 10 Aug 2026 08:44:04 +0000 Subject: [PATCH 2/5] fix(running-tend): don't let the overlay's literal 24 reopen the window gap --- .claude/skills/running-tend/SKILL.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.claude/skills/running-tend/SKILL.md b/.claude/skills/running-tend/SKILL.md index 11e7df23..e12888bf 100644 --- a/.claude/skills/running-tend/SKILL.md +++ b/.claude/skills/running-tend/SKILL.md @@ -38,10 +38,14 @@ Pass extra prefixes when running token reports or listing runs so these workflows are included: ```bash -"${CLAUDE_PLUGIN_ROOT}/scripts/token-report.sh" 24 "review-" +"${CLAUDE_PLUGIN_ROOT}/scripts/token-report.sh" "${HOURS:-24}" "review-" TARGET_REPO=max-sixty/tend "${CLAUDE_PLUGIN_ROOT}/scripts/list-recent-runs.sh" "tend-" "review-" ``` +Under `review-runs`, `$HOURS` is the lookback derived from its Step 1 anchor — +passing a literal `24` there reopens the window gap that anchor closes. The +default keeps an ad-hoc invocation working. + ## Labels - `claude-behavior` — findings from `review-reviewers` From 74328fc2898f386fd4569fcfdd392a88db92da80 Mon Sep 17 00:00:00 2001 From: tend-agent <270458913+tend-agent@users.noreply.github.com> Date: Mon, 10 Aug 2026 08:50:08 +0000 Subject: [PATCH 3/5] fix(review-runs): census on completion, so long runs can't fall between windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The heading said 'completed since the previous run' but the query filtered created>=$SINCE. A run created before the anchor and still in progress at the predecessor's census was dropped there by status=completed and dropped again here — never censused by anyone, and that is exactly the long-running class Step 3 hunts for. Over-fetch by created and filter on updated_at, the shape list-recent-runs.sh already uses. The floor is 24h, a whole run lifetime rather than the 6h job cap: created_at starts at queue time, and a cancel-in-progress: false group can hold a run queued for hours before execution begins. Also trims Step 1's lead comment per CLAUDE.md's brevity rule. --- .../skills/review-runs/SKILL.md | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/plugins/tend-ci-runner/skills/review-runs/SKILL.md b/plugins/tend-ci-runner/skills/review-runs/SKILL.md index cc0a155f..a45103a5 100644 --- a/plugins/tend-ci-runner/skills/review-runs/SKILL.md +++ b/plugins/tend-ci-runner/skills/review-runs/SKILL.md @@ -130,13 +130,9 @@ List tend CI runs that completed since the previous `review-runs` run (nominally ```bash REPO=$(gh repo view --json nameWithOwner --jq '.nameWithOwner') -# Anchor the window on the predecessor's start, not on a duration back from -# `now`. A `date -u -d '24 hours ago'` resolves when the *agent* runs it — the -# run's start plus container boot and skill loading — so it opens strictly after -# the predecessor started and drops every run in that band, which is exactly -# the work the predecessor triggered. Anchoring makes consecutive windows tile, -# and every later step reads the same `$SINCE` instead of recomputing a -# duration that has drifted further by then. +# Anchor on the predecessor's start: a `date -d '24 hours ago'` resolves when +# the agent runs it, so the window opens after the predecessor started and +# drops the band in between. Later steps read this same `$SINCE`. # # Derive the workflow id from this run rather than assuming the file name; # exclude this run, because a re-run attempt of it can already read as @@ -163,15 +159,25 @@ if [[ "$SINCE" < "$FLOOR" ]]; then SINCE=$FLOOR; fi # applying it per page is harmless. PREFIXES=("tend-") PREFIX_RE="^($(IFS='|'; echo "${PREFIXES[*]}"))" +# Census on *completion*, which is the axis that tiles. A run created before +# `$SINCE` may still have been in progress at the predecessor's census, so +# `status=completed` dropped it there — filtering on `created` here would drop +# it again and nobody would ever see it, and those are the long-running runs +# Step 3 goes on to hunt. So over-fetch by `created` and filter on +# `updated_at`, as `list-recent-runs.sh` does. The floor is a run's whole +# lifetime, not its job cap: `created_at` starts at queue time, and a +# `cancel-in-progress: false` group can hold a run queued for many hours +# before its 6h of execution even begins. +FETCH_FROM=$(date -u -d "$SINCE - 24 hours" +%Y-%m-%dT%H:%M:%SZ) for workflow in $(gh api --paginate repos/$REPO/actions/workflows --jq ".workflows[] | select(.name | test(\"$PREFIX_RE\")) | .id"); do - gh api --paginate "repos/$REPO/actions/workflows/$workflow/runs?created=>=$SINCE&status=completed&per_page=100" \ - --jq '.workflow_runs[] | {databaseId: .id, conclusion, createdAt: .created_at, name: .name}' + gh api --paginate "repos/$REPO/actions/workflows/$workflow/runs?created=>=$FETCH_FROM&status=completed&per_page=100" \ + --jq ".workflow_runs[] | select(.updated_at >= \"$SINCE\") | {databaseId: .id, conclusion, createdAt: .created_at, updatedAt: .updated_at, name: .name}" done ``` If no runs found, report "no runs to review" and exit. -Report the run census as the count this returns. Cross-check any workflow whose count lands on a round page boundary (30, 100) against `.total_count` before trusting it — a count that equals the page size is the signature of a page that was never followed. +Report the run census as the count this returns. `.total_count` counts the wider `FETCH_FROM` fetch, so it bounds the census from above rather than matching it — but a census that lands on a round page boundary (30, 100) is still the signature of a page that was never followed, so check that one against `.total_count` before trusting it. Then, for each run ID from above, pull its jobs and classify them: From 28ab503921162f29f5c9b2f91051921f6d1a182f Mon Sep 17 00:00:00 2001 From: tend-agent <270458913+tend-agent@users.noreply.github.com> Date: Mon, 10 Aug 2026 08:59:56 +0000 Subject: [PATCH 4/5] fix(review-runs): persist the anchor, so Steps 2 and 4 stop reading an empty $SINCE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Step 1 sets $SINCE in one Bash tool call; Steps 2 and 4 read it in others, where shell state is gone. Neither failed loudly: `date -d ""` is today's midnight rather than an error, so Step 2's HOURS became hours-since-midnight (9 instead of 25 on this cron) and priced a narrower band than the literal 24 it replaced. Step 4's `closedAt > "$SINCE"` compared against the empty string, admitting every closed bot PR ever — 62 instead of 10 here — so that cross-check was never windowed at all, predating this PR. Step 1 now writes the clamped anchor to /tmp/review-runs-since and both readers cat it back. --- .../tend-ci-runner/skills/review-runs/SKILL.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/plugins/tend-ci-runner/skills/review-runs/SKILL.md b/plugins/tend-ci-runner/skills/review-runs/SKILL.md index a45103a5..5e7fb352 100644 --- a/plugins/tend-ci-runner/skills/review-runs/SKILL.md +++ b/plugins/tend-ci-runner/skills/review-runs/SKILL.md @@ -132,7 +132,8 @@ List tend CI runs that completed since the previous `review-runs` run (nominally REPO=$(gh repo view --json nameWithOwner --jq '.nameWithOwner') # Anchor on the predecessor's start: a `date -d '24 hours ago'` resolves when # the agent runs it, so the window opens after the predecessor started and -# drops the band in between. Later steps read this same `$SINCE`. +# drops the band in between. Steps 2 and 4 re-read the anchor from the file +# written below; shell variables don't survive between Bash tool calls. # # Derive the workflow id from this run rather than assuming the file name; # exclude this run, because a re-run attempt of it can already read as @@ -148,6 +149,7 @@ PREV_START=$(gh api "repos/$REPO/actions/workflows/$WF_ID/runs?status=success&pe SINCE=${PREV_START:-$(date -u -d '25 hours ago' +%Y-%m-%dT%H:%M:%SZ)} FLOOR=$(date -u -d '49 hours ago' +%Y-%m-%dT%H:%M:%SZ) if [[ "$SINCE" < "$FLOOR" ]]; then SINCE=$FLOOR; fi +echo "$SINCE" > /tmp/review-runs-since # Add the repo's extra prefixes from its `running-tend` skill: any workflow # running the tend action is in scope, not just the generated `tend-*` ones. # Step 2 prices the same list. @@ -204,7 +206,11 @@ Run the token report script to get per-run token counts: ```bash # Whole hours back to Step 1's anchor, rounded up so the whole band is priced. # A literal `24` here reopens the same gap Step 1 closed, and clips a wider band -# than Step 1 did because `now` moved on during the session. +# than Step 1 did because `now` moved on during the session. Re-read the anchor +# from Step 1's file: `$SINCE` was set in a different Bash call and is empty +# here, and `date -d ""` is today's midnight rather than an error, so the +# arithmetic would quietly yield hours-since-midnight instead. +SINCE=$(cat /tmp/review-runs-since) HOURS=$(( ( $(date -u +%s) - $(date -u -d "$SINCE" +%s) + 3599 ) / 3600 )) "${CLAUDE_PLUGIN_ROOT}/scripts/token-report.sh" "$HOURS" > /tmp/token-report.json ``` @@ -231,7 +237,10 @@ For each analyzed run, compare what the bot did against what happened next. The mention, notifications, weekly, and review-reviewers runs get the same treatment: find the bot's output and check whether it was accepted. ```bash -# Example: check if a bot PR was merged or closed +# Example: check if a bot PR was merged or closed. Re-read Step 1's anchor — +# unset here, an empty string compares less than every non-null `closedAt` and +# the check silently stops being windowed at all. +SINCE=$(cat /tmp/review-runs-since) gh pr list --author "$BOT_LOGIN" --state all --json number,title,state,closedAt \ --jq '.[] | select(.closedAt > "'$SINCE'")' ``` From 58bd47e04a4b752ae5b15f3aa4354490c0005d47 Mon Sep 17 00:00:00 2001 From: tend-agent <270458913+tend-agent@users.noreply.github.com> Date: Mon, 10 Aug 2026 09:07:50 +0000 Subject: [PATCH 5/5] docs(review-runs): trim Step 2's comment to the part that isn't recoverable --- plugins/tend-ci-runner/skills/review-runs/SKILL.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/plugins/tend-ci-runner/skills/review-runs/SKILL.md b/plugins/tend-ci-runner/skills/review-runs/SKILL.md index 5e7fb352..ed1a2836 100644 --- a/plugins/tend-ci-runner/skills/review-runs/SKILL.md +++ b/plugins/tend-ci-runner/skills/review-runs/SKILL.md @@ -205,11 +205,8 @@ Run the token report script to get per-run token counts: ```bash # Whole hours back to Step 1's anchor, rounded up so the whole band is priced. -# A literal `24` here reopens the same gap Step 1 closed, and clips a wider band -# than Step 1 did because `now` moved on during the session. Re-read the anchor -# from Step 1's file: `$SINCE` was set in a different Bash call and is empty -# here, and `date -d ""` is today's midnight rather than an error, so the -# arithmetic would quietly yield hours-since-midnight instead. +# A literal `24` reopens the gap Step 1 closed. The `cat` isn't optional: an +# unset `$SINCE` makes `date -d ""` today's midnight, not an error. SINCE=$(cat /tmp/review-runs-since) HOURS=$(( ( $(date -u +%s) - $(date -u -d "$SINCE" +%s) + 3599 ) / 3600 )) "${CLAUDE_PLUGIN_ROOT}/scripts/token-report.sh" "$HOURS" > /tmp/token-report.json