Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
6510263
The by-CPU tools rank by CPU, not elapsed time (#3523) (#3552)
erikdarlingdata Sep 18, 2026
1900967
analyze_server: an empty analysis window is "unavailable", not an all…
erikdarlingdata Sep 18, 2026
3541662
PG MCP verdicts: classify autovacuum from the ranking's GREATEST, pic…
erikdarlingdata Sep 18, 2026
0c86d7a
Fix the honesty trio: fake granted-memory zero, dropped physical-read…
erikdarlingdata Sep 18, 2026
deb547a
The xmin horizon alert gains a horizon-persistence arm and an observa…
erikdarlingdata Sep 18, 2026
fa5c1ec
get_pg_plans pins query_id in the SQL; get_pg_io_stats reports track_…
erikdarlingdata Sep 18, 2026
69a248b
Perfmon analysis reads divide the per-interval delta by its measured …
erikdarlingdata Sep 18, 2026
537cbbd
Floor the SQL count thresholds, give Store Disk Pressure a GB floor, …
erikdarlingdata Sep 18, 2026
b74a22c
Schedule surfaces refuse delta-family cadences past the shared gap po…
erikdarlingdata Sep 18, 2026
ca10188
Viewer Recommendations tabs render a window-empty notice instead of t…
erikdarlingdata Sep 18, 2026
ec195bc
get_memory_trend joins the grants series so total_granted_mb carries …
erikdarlingdata Sep 18, 2026
e43e846
The daily classifier bands deadlocks as a rate through the card band'…
erikdarlingdata Sep 18, 2026
dcd7cb2
Procedures slicer plots the physical series on a physical sort; Query…
erikdarlingdata Sep 18, 2026
7c9e133
CHANGELOG: the brains-review wave, one splice (17 entries) (#3568)
erikdarlingdata Sep 18, 2026
cec5351
Dashboard analysis perfmon reads divide by the measured interval (#35…
erikdarlingdata Sep 18, 2026
5347140
Viewer pass for #3528: Settings box for the Store Disk Pressure GB fl…
erikdarlingdata Sep 18, 2026
cebb3d8
The store TLS root certificate gets a unique per-generation name, so …
erikdarlingdata Sep 18, 2026
bffca6b
CHANGELOG: the #3557 unique-root-name entry (post-splice follow-up) (…
erikdarlingdata Sep 18, 2026
a6b2697
The Locking & Contention grid names the table as schema.table, so the…
erikdarlingdata Sep 18, 2026
a993e50
Extreme, corroborated anomaly findings can now cross the notify floor…
erikdarlingdata Sep 18, 2026
f85c801
get_store_metrics: the job_history block now says whose eyes its rows…
erikdarlingdata Sep 18, 2026
552a7bf
The forced-plan-failures alert read gets the covering index the plann…
erikdarlingdata Sep 18, 2026
bebdf55
The viewer's tray toast honors mute rules itself, so a Snooze stops t…
erikdarlingdata Sep 18, 2026
9df2a91
The compression-stuck check confirms a -infinity read before it pages…
erikdarlingdata Sep 18, 2026
91029fb
Text on an accent fill gets a measured ink in every theme, and the Fi…
erikdarlingdata Sep 18, 2026
0516ac4
MCP pages now say what bounded them: caps bind to the caller's limit,…
erikdarlingdata Sep 18, 2026
3a04de2
The duration-trend trio routes by retention tier and discloses its so…
erikdarlingdata Sep 18, 2026
950fad3
The four naked delta families store the interval their deltas accrued…
erikdarlingdata Sep 18, 2026
24c3508
Blocking and CPU bands are rates over the window they were measured i…
erikdarlingdata Sep 18, 2026
1051c50
SQL Server's Poison Wait alert measures accumulated starvation over a…
erikdarlingdata Sep 18, 2026
2df8fde
Analysis facts divide by the time the collector actually observed, an…
erikdarlingdata Sep 18, 2026
317c54d
The delta calculator's doc points at the census that actually guards …
erikdarlingdata Sep 18, 2026
6aa67c2
Lite's get_query_trend carries the disclosure block its Darling twin …
erikdarlingdata Sep 18, 2026
4087d27
get_store_metrics' inventory was blind to 57% of the store it reporte…
erikdarlingdata Sep 18, 2026
561df60
The operator can maintain the twelve palette colors of each theme fro…
erikdarlingdata Sep 18, 2026
98069fd
Every delta family is seeded from the store at service start, and the…
erikdarlingdata Sep 18, 2026
52788ae
MCP percents name their denominator: shares are of the window or say …
erikdarlingdata Sep 18, 2026
b6d60dd
The twenty continuous aggregates join the compression ladder: compres…
erikdarlingdata Sep 18, 2026
fa0d15c
Swapping the theme-text provider empties the stock-palette cache (#35…
erikdarlingdata Sep 18, 2026
4243d67
Two TimescaleSupport doc sentences #3610 falsified are corrected: the…
erikdarlingdata Sep 18, 2026
90d92b2
MCP write tools report what happened: every server you add lands in a…
erikdarlingdata Sep 18, 2026
bfc17bf
The scorer's wait thresholds carry their measurement: WRITELOG stops …
erikdarlingdata Sep 18, 2026
fb4133d
Slack analysis pages stay inside the 50-block message limit: details …
erikdarlingdata Sep 18, 2026
1a14bd2
Continuous-aggregate materializations are chunked at one raw chunk, l…
erikdarlingdata Sep 18, 2026
710c5f3
ObservedHours documents and pins the zero-coverage fallback: nominal …
erikdarlingdata Sep 18, 2026
f1cd10b
The PostgreSQL first-target runbook stops denying three capabilities …
erikdarlingdata Sep 18, 2026
dcd3482
Web TLS: a not-yet-valid certificate is loopback-only but read as hea…
erikdarlingdata Sep 18, 2026
427fa65
The light themes' Warning and Info status colors read against their o…
erikdarlingdata Sep 18, 2026
c0d3452
The compression dead-job alert says what a -infinity row IS on the st…
erikdarlingdata Sep 18, 2026
665de2f
Slack text cuts land on whole characters, not UTF-16 indexes: an emoj…
erikdarlingdata Sep 18, 2026
14aed22
Forced Plan Failing fires once per observation, not once per cooldown…
erikdarlingdata Sep 18, 2026
b62a00e
The File Growth threshold means one thing — megabytes per hour — at e…
erikdarlingdata Sep 18, 2026
e609dbe
Story confidence measures corroboration instead of path length, and t…
erikdarlingdata Sep 18, 2026
f9797a8
The daily digest and sweep rollup asked 'did I send' when the channel…
erikdarlingdata Sep 18, 2026
88be048
A server nothing has banded yet is Unknown, not Healthy, and the aler…
erikdarlingdata Sep 18, 2026
2a1e367
Every delta family now stores the interval its deltas accrued over, a…
erikdarlingdata Sep 18, 2026
ee07b5c
Database File Growth's rise arm fires once per hourly observation, no…
erikdarlingdata Sep 18, 2026
a33eaf3
DeliverAndReportAsync is a REQUIRED member of IAlertDeliverer, not a …
erikdarlingdata Sep 18, 2026
f50a6ab
compare_analysis bands each delta by the server's own dispersion and …
erikdarlingdata Sep 18, 2026
32389e6
Every latest-snapshot MCP read says when it was captured, and no tool…
erikdarlingdata Sep 18, 2026
5cd6513
PostgreSQL servers get a measured deadlock band from their own counte…
erikdarlingdata Sep 18, 2026
3c50b91
get_pg_logging_audit judges a PostgreSQL target's logging settings fa…
erikdarlingdata Sep 18, 2026
f681802
The daily summary stops painting purged months green, and every MCP f…
erikdarlingdata Sep 18, 2026
6b837f9
The interval-hourly refresh recomputes one fleet-wide bucket per dirt…
erikdarlingdata Sep 18, 2026
dd1061a
Top CPU Queries renders as one record per query on Slack, not seven i…
erikdarlingdata Sep 18, 2026
fb686ed
A stock PostgreSQL target without pg_wait_sampling has no wait histor…
erikdarlingdata Sep 18, 2026
9708928
Top Cpu Queries' Max Dop is the newest plan's reading with the cross-…
erikdarlingdata Sep 18, 2026
f88fe32
Zero is a measurement: health parsers say whether their source was ev…
erikdarlingdata Sep 18, 2026
c39de4c
A Claude review that posted nothing no longer finishes green: read-on…
erikdarlingdata Sep 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
154 changes: 151 additions & 3 deletions .github/workflows/claude-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ name: Claude Auto Review
# enforcement lives in claude-review-guard.yml, whose verdict arm fails while the newest verdict
# is changes-requested, so a substantive finding becomes a red check instead of a comment that
# auto-merge outruns (the #3470-#3473 train shipped six findings in one night that way; one was
# real). It no-ops cleanly until the CLAUDE_CODE_OAUTH_TOKEN repo secret is set, and on fork PRs
# (which do not receive secrets), so neither case shows a failed check.
# real). Since #3650 the job also fails ITSELF when the run submitted no verdict, with the cause in
# its log and the transcript attached -- a green here means a verdict exists, and the guard is the
# second line. It no-ops cleanly until the CLAUDE_CODE_OAUTH_TOKEN repo secret is set, and on fork
# PRs (which do not receive secrets), so neither case shows a failed check.
on:
pull_request:
types: [opened, synchronize, reopened]
Expand Down Expand Up @@ -37,7 +39,19 @@ jobs:
with:
fetch-depth: 1

# #3650: the verdict check at the bottom counts the bot's formal reviews submitted AFTER this
# instant, so a verdict left by an earlier run on the same PR cannot vouch for this one. Read
# once, here, before the action installs anything -- GitHub stamps submitted_at in the same
# ISO-8601 UTC shape (2026-09-18T22:00:00Z), so the comparison below is a plain string one.
- name: Open the verdict window
id: window
if: ${{ env.CLAUDE_CODE_OAUTH_TOKEN != '' }}
run: echo "start=$(date -u +%FT%TZ)" >> "$GITHUB_OUTPUT"

# The step NAME is read by claude-review-guard.yml (REVIEW_STEP) to tell a clean no-op from a
# run that said nothing; renaming it blinds the guard. The id is for the steps below.
- name: Claude review
id: review
if: ${{ env.CLAUDE_CODE_OAUTH_TOKEN != '' }}
uses: anthropics/claude-code-action@v1
with:
Expand Down Expand Up @@ -78,5 +92,139 @@ jobs:
does not enable, and no gate needs it. The guard's rule is newest-verdict-wins: on
a re-review after new commits, review the NEW diff and submit a fresh verdict, and
a clean fresh verdict clears an earlier changes-requested by itself.
# #3650: the prompt above says the branch is checked out and asks for a correctness,
# parity and security review -- an invitation to read code -- while the allowlist used to
# permit only the four gh verbs and the inline-comment tool. Every Read, Grep, Glob and
# git call the reviewer reached for was a permission denial, and the swallowed runs' own
# result blocks put the ratio on record: 50 turns / 18 denials, 39 / 21, 18 / 17, each
# ending subtype=success with NOTHING posted. After enough denials the session ends
# without ever reaching the verdict protocol, so a paid run leaves no trace (three PRs,
# about thirteen runs, one night). Read-only tools are enough: the reviewer needs to open
# the files the diff touches, find a symbol's other callers and read a parity twin, and
# nothing about reviewing needs a write. Nothing here can edit, commit, push or post
# outside the gh verbs already listed; the git verbs are the read-only three.
claude_args: |
--allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr review:*)"
--allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr review:*),Read,Grep,Glob,Bash(git diff:*),Bash(git log:*),Bash(git show:*)"

# #3650: a review that posted nothing used to finish GREEN. The prompt mandates one formal
# review per run -- changes-requested on a substantive finding, a "LGTM" comment review
# otherwise -- so on this repo a run that submitted no verdict has broken its contract every
# time; a clean review is never silent, and a zero here is never legitimate. This step turns
# that into the job's own colour, with the diagnosis in the log, so the guard (#2229/#3492,
# still the REQUIRED check, still enforcing newest-verdict-wins and the drift arm) becomes the
# second line rather than the first. Counted: reviews by the bot, submitted inside this run's
# window, with a NON-EMPTY body. The body test is load-bearing: the inline-comment tool files
# each comment inside a review of its own with an empty body (#3647 carried four bot reviews,
# two of them bodiless carriers), and both verdict shapes must carry one (gh and the API both
# refuse a bodiless comment/changes-requested review) -- so "posted inline notes, never a
# verdict" is the #3470 shape and fails here, as it should.
# Two runs end with zero verdicts and both are defects on this repo, told apart by whether
# Claude ran at all: the action exits SUCCESS in seconds without running when this file
# differs from the default branch's copy (cause 1 in the guard's header), and then it sets no
# execution_file and writes no transcript. Expected on a PR that edits this file; a repo-wide
# outage otherwise -- the guard grades which, because it can read the PR's file list from a
# workflow that is free to change. The step's own outcome is left to speak for itself when it
# is not success (a failed step already reds the job; a cancelled one is a superseded push).
# A gh lookup failure is NOT a verdict on the review (#2309): it warns and stands down rather
# than forcing a paid re-run of the whole job to clear a transient API error -- the guard's
# own tally, a separate and free-to-rerun workflow, stays the enforcement.
- name: Verify the review posted a verdict
if: ${{ always() && env.CLAUDE_CODE_OAUTH_TOKEN != '' }}
env:
GH_TOKEN: ${{ github.token }}
R: ${{ github.repository }}
PR: ${{ github.event.pull_request.number }}
SINCE: ${{ steps.window.outputs.start }}
REVIEW_OUTCOME: ${{ steps.review.outcome }}
# Set by the action only after Claude actually ran; empty when it refused at validation.
EXECUTION_FILE: ${{ steps.review.outputs.execution_file }}
# Author of every artifact the review leaves behind (the action's bot_name default).
REVIEW_BOT: claude[bot]
run: |
set -euo pipefail
summary() { echo "$*" >> "$GITHUB_STEP_SUMMARY"; }

if [ "$REVIEW_OUTCOME" != "success" ]; then
echo "::notice title=Verdict check skipped::The review step ended '$REVIEW_OUTCOME'; its own"\
"outcome carries the story, so this step has nothing to add."
exit 0
fi

transcript="${EXECUTION_FILE:-$RUNNER_TEMP/claude-execution-output.json}"

errfile=$(mktemp 2>/dev/null || echo /dev/null)
if ! matched=$(gh api --paginate "repos/$R/pulls/$PR/reviews?per_page=100" \
--jq ".[] | select(.user.login == env.REVIEW_BOT
and .submitted_at != null
and .submitted_at >= env.SINCE
and ((.body // \"\") | length) > 0)
| \"\\(.state)\\t\\(.submitted_at)\\t\\(.html_url)\"" 2>"$errfile"); then
err=$(cat "$errfile" 2>/dev/null || true)
[ "$errfile" != /dev/null ] && rm -f "$errfile" || true
echo "::warning title=Verdict check could not read the PR's reviews::gh api"\
"repos/$R/pulls/$PR/reviews failed: ${err:-no stderr}. A lookup failure is not a"\
"review verdict (#2309), so the review is UNCONFIRMED here; the guard's own tally"\
"decides, or read the PR by eye."
summary "- Verdict check: lookup failed; review UNCONFIRMED (#2309)."
exit 0
fi
[ "$errfile" != /dev/null ] && rm -f "$errfile" || true

count=$(printf '%s' "$matched" | grep -c . || true)
echo "verdict reviews by $REVIEW_BOT on PR #$PR since $SINCE: $count"
if [ -n "$matched" ]; then printf '%s\n' "$matched"; fi

if [ "$count" -gt 0 ]; then
summary "### Claude review posted $count verdict review(s) since $SINCE"
exit 0
fi

summary '### Claude review posted NO verdict'

if [ -z "$EXECUTION_FILE" ] && [ ! -s "$transcript" ]; then
echo "::error title=Claude never ran::claude-code-action exited success without running"\
"Claude -- no execution file, no transcript. That is what it does when this branch's"\
".github/workflows/claude-review.yml differs from the default branch's copy (#2229,"\
"cause 1). Expected on a PR that edits this file; on any other PR it means the file"\
"has drifted and EVERY PR in the repo is going unreviewed -- see the guard's verdict"\
"on this PR. Either way, do not read this PR as reviewed."
summary '- Claude never ran (workflow validation refused). Not reviewed.'
exit 1
fi

echo "::error title=Review ran and posted no verdict::The review ran and posted no"\
"verdict: $REVIEW_BOT submitted no formal review on PR #$PR since $SINCE, and the"\
"prompt promises one every run even when clean. Real money was spent and the output"\
"vanished (#3650; the #2229 failure shape). Do NOT read this PR as reviewed. The"\
"transcript is attached to this run as the claude-review-transcript artifact; its"\
"result block follows."
summary '- The review ran and submitted no formal review. Not reviewed (#3650).'
if [ -s "$transcript" ]; then
echo "--- result block of $transcript ---"
jq -c '.[-1] | {type, subtype, is_error, num_turns, permission_denials_count,
total_cost_usd, duration_ms,
denied: [.permission_denials[]? | .tool_name]}' "$transcript" \
2>/dev/null || true
echo "--- tail of $transcript ---"
tail -c 4000 "$transcript" || true
echo
else
echo "(no transcript at $transcript)"
fi
exit 1

# #3650: the per-turn transcript is what proves WHY a run said nothing, and it used to die
# with the runner -- the denial-ratio evidence above had to be inferred from result blocks in
# the step log. Seven days is long enough to diagnose the next swallowed run in one click and
# short enough that nothing accumulates. The reviewer's tools are read-only over a public
# tree and gh reads of public PR data, so the transcript holds nothing that is not already
# public; keep the allowlist that way, because this artifact is readable by anyone who can
# read the repo. Missing file (the action refused to run, or was skipped) is not an error.
- name: Retain the review transcript
if: ${{ always() && env.CLAUDE_CODE_OAUTH_TOKEN != '' }}
uses: actions/upload-artifact@v6
with:
name: claude-review-transcript
path: ${{ steps.review.outputs.execution_file || format('{0}/claude-execution-output.json', runner.temp) }}
if-no-files-found: ignore
retention-days: 7
Loading
Loading