Repository navigation
get_pg_blocking/get_pg_wait_stats/get_pg_index_usage move reading guidance off tools/list (#3898) - #4087
Merged
Conversation
…dance off tools/list (#3898) Splits each tool's Description into a terse HEAD (served by tools/list, under 620 chars) and a TAIL carrying every original sentence verbatim, served on demand by get_tool_guide. Trims get_pg_wait_stats' and get_pg_index_usage's over-cap `limit` parameter descriptions (D2), moving the cut text verbatim into each tool's own tail. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kukh6S1arS5ffv7fcEhddd
erikdarlingdata
marked this pull request as ready for review
September 23, 2026 22:55
erikdarlingdata
enabled auto-merge (squash)
September 23, 2026 22:55
erikdarlingdata
added a commit
that referenced
this pull request
Sep 24, 2026
* Add 48 buffered CHANGELOG entries in one splice Adds 48 entries and 48 link refs (#4057, #4077, #4079, #4081, #4082, #4083, #4084, #4085, #4086, #4087, #4088, #4089, #4090, #4091, #4092, #4093, #4095, #4096, #4099, #4100, #4101, #4103, #4105, #4106, #4107, #4108, #4109, #4110, #4111, #4113, #4114, #4115, #4116, #4117, #4118, #4119, #4120, #4121, #4122, #4123, #4124, #4125, #4126, #4127, #4131, #4133, #4136, #4141). Each PR's entry was buffered, and this lands every entry whose PR was merged on origin/dev when it ran. Entries found under a bare section line (none unless the old script ran again) move into the matching ### section, below the new entries. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC * Changelog splice: add #4137's entry, which merged before the splice but was missed #4137 (pg_plan_capture reads csvlog) merged at 05:57Z with a CHANGELOG entry section in its body. It was neither spliced nor listed as needing no entry. Its entry goes under Fixed, right after #4136's, and #4136's last sentence now points to it instead of saying plan capture is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NUU29PuGg9TUFBsACgZg2K --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #3898.
Why
Issue #3898: a client that loads Darling's whole MCP catalog pays for every tool description before it asks a
single question.
get_pg_blocking,get_pg_wait_statsandget_pg_index_usagewere three of the biggest. ThisPR moves their long reading guidance off
tools/list.get_tool_guidenow serves that guidance on demand.Nothing is deleted.
What changes
Each tool's
Descriptionnow carries a short HEAD.tools/listserves the head, and it is at most 620characters. After the head is a
<<GUIDE>>marker. After the marker is a TAIL that keeps every sentence of theoriginal description.
get_tool_guideserves the tail by tool name.event log. An empty answer means "none was sampled," not "none happened." The head also states that deadlocks
(cycles) are reported separately from chains. It states that
root_backend_idis a string, and it must neverbe compared as a number. It also names which root state needs an application fix versus a query fix.
engine_cumulativetier. Anon-Aurora target needs
get_pg_wait_samplinginstead. The head also states the wire unit (milliseconds). Itstates the page rule this tool family already uses elsewhere too: the page is bounded by
limit. The shares arealways of the whole window, never of the page.
scans_in_windowfigure, not thelifetime counter, is what to read for disuse. Second, a drop recommendation is gated on catalog facts: primary
key, unique or exclusion constraint, replica identity, partial, expression, invalid. It also needs at least two
samples. Third, the collector runs on writers only, so a replica's own counts do not describe the writer. The head also states the 64 kB size floor, which is why this tool's row count is lower than
get_pg_index_bloat's.D2 parameter caps
get_pg_wait_stats' andget_pg_index_usage'slimitparameter descriptions were 214 and 257 characters. Bothwere over the 200-character cap, so both were trimmed.
get_pg_wait_stats.limit(214 to 190 characters): cut only the trailing clausewhatever this is set to.Thatexact phrase now appears in the tool's own tail.
get_pg_index_usage.limit(257 to 93 characters): moved a whole guardrail sentence into the tool's tail,verbatim (see the diff for the exact text). That sentence names the page bound, the
truncatedflag and theone-row-past-the-cap check. The parameter now just points to the reading guide. This matches the pattern the
pgA lane already used for
get_pg_deadlocksandget_pg_index_bloat.McpPageContractTestschecks thatpattern by reading a tool's tail once the tool carries the
<<GUIDE>>marker.No renames, no consolidation, no parameter added or removed (D8). No SQL, payload or result-note change (D7).
Before/after served characters
Darling only. None of the three tools has a Lite twin.
Corrections
None. I checked the "reports in microseconds" clause in
get_pg_wait_stats' original text againstDarlingPgWaitReader. The store keeps microseconds. The reader divides by 1,000 before the tool ever sees a row,so the wire values are milliseconds. That original clause describes PostgreSQL's own instrumentation, not this
tool's output, so I left it as written. I stated the wire unit plainly in the new head instead ("Values are
milliseconds").
D4 removals
None. None of the three original descriptions carried an issue reference or an anecdote.
Re-pointed pins
None of the existing description pins (
McpZeroIsAMeasurementTests,McpDescriptionTruthPinTests) mention thesethree tools. One test needed no code change but did drive an implementation choice. That test is
McpPageContractTests.EveryPgPagedToolThroughTheHelper_FetchesOnePastTheCap_AndObservesThroughBoundPage. Italready reads a converted tool's tail instead of its parameter, and its own comment names the pgA precedent. I
matched what it expects by moving
get_pg_index_usage's full paging sentence into the tail.D9 traps
get_pg_blockingreturns an emptychainslist. Did no blocking happen?Correct: unknown either way. Blocking is sampled periodically. Empty means none was SAMPLED, not none
happened. Anything shorter than the collection interval is invisible.
Prevents it: "a periodic SAMPLE, not an event log... empty means none was SAMPLED, not none happened."
get_pg_blockingreturns chains with rows. Does that also account for every deadlock?Correct: no. Cycles (deadlocks) are reported separately from chains. They are never folded into a chain's
totals.
Prevents it: "Cycles (deadlocks) are reported separately, never folded into a chain."
get_pg_blockingcaptures show closeroot_backend_idvalues read as numbers. Same backend?Correct: not necessarily.
root_backend_idis a string built from a 64-bit composite. Parsing it as anumber can round two different backends onto the same id.
Prevents it: "root_backend_id is a STRING; compare as text, never as a number."
get_pg_blocking's root is "idle in transaction." Does the query it is running need tuning?Correct: no. That root is running nothing. It is an application defect, such as a dangling transaction,
not a query to tune.
Prevents it: "idle in transaction: an application defect; active: a query to tune."
get_pg_wait_statsis empty for a self-hosted, non-Aurora target. No waits?Correct: no evidence either way. This tool is Aurora-only. A non-Aurora target needs
get_pg_wait_samplinginstead.Prevents it: "Aurora only... stock targets are served by get_pg_wait_sampling instead."
get_pg_wait_statsreturns 3 rows whosepct_of_total_waitsum to 40%. Were 60% of waits missed?Correct: no. The page was capped by
limit(truncated: true). Shares are always of the whole window,never of the page, so a partial page never sums to 100%.
Prevents it: "THE PAGE IS BOUNDED BY limit... SHARES ARE OF THE WINDOW, NOT OF THE PAGE."
get_pg_wait_stats'total_wait_time_msreads 1,500. Is that microseconds, like the source?Correct: no. The wire value is milliseconds. The tool converts before serving.
Prevents it: "Values are milliseconds."
get_pg_index_usageshows 40,000 lifetime scans and 0 scans in the window. Is the index unused?Correct: not from the lifetime figure alone.
scans_in_windowis what to read, not the lifetime counter.It still needs a structural-blocker check plus two or more samples.
Prevents it: "scans_in_window, not lifetime total_scans_since_stats_reset, is the disuse figure."
Correct: no. It is a candidate to investigate, never a conclusion.
Prevents it: "unscanned_without_a_structural_blocker is a candidate, never a conclusion."
Correct: no. Fewer than two samples is not enough history to judge disuse at all. It reads Healthy, not
Warning.
Prevents it: "needs 2+ samples."
Correct: no. Droppability is gated on catalog facts: primary key, unique or exclusion constraint, replica
identity, partial, expression, invalid. Any of those blocks the drop, regardless of the scan count.
Prevents it: "Droppability is GATED on primary key/unique/exclusion/replica-identity/partial/
expression/invalid facts."
get_pg_index_usagewas read against a read replica and shows 0 scans. Unused on the writer too?Correct: not determinable this way. This collector runs on writers only. A replica's own scan counts are
not the writer's.
Prevents it: "WRITERS ONLY - a replica's scan counts are its own, not the writer's."
get_pg_index_usagereturns fewer indexes thanget_pg_index_bloatfor the same server. Missing rows?Correct: no.
get_pg_index_usagefloors at 64 kB.get_pg_index_bloathas no size floor. The gap isthat floor, not missing objects.
Prevents it: "Floors at 64 kB (plus any INVALID index), so counts run lower than get_pg_index_bloat's
floor-free census."
get_pg_wait_stats'limitdrops from 20 to 5. Doespct_of_total_waitnow divide by a smallernumber?
Correct: no. The shares stay of the whole window no matter what
limitis set to. Only the page's ownreach shrinks.
Prevents it: "SHARES ARE OF THE WINDOW, NOT OF THE PAGE," restated on the
limitparameter itself.get_pg_blocking'scyclesarray is empty whilechainshas rows. No deadlocks at all, ever?Correct: none were captured in this window.
cycles_sampledalways rides besidechains. Its absence isnever mistaken for "not checked." It is still a periodic sample, not a guarantee for all time.
Prevents it: the head's periodic-sample caveat plus "Cycles ... reported separately."
Test plan
dotnet build Darling/Darling.Tests/Darling.Tests.csproj. Build succeeded, 0 Warning(s), 0 Error(s).McpToolGuideHeadsPgB*,McpToolsListBudgetTests,McpPageContractTests,DarlingMcpPgPercentDenominatorTests,PgWaitInstrumentDisclosureTests,McpPayloadContractCensusTests,DarlingPgBlockingReaderTests,DarlingPgIndexUsageReaderTests,PgInt64IdentityWireShapeTests,DarlingPgWaitReaderTests,McpZeroIsAMeasurementTests,McpToolGuideTests. All green (Total: 246,Failed: 0).
dropcheck.py origin/dev HEAD get_pg_blocking get_pg_wait_stats get_pg_index_usage):get_pg_blocking [Darling] old=1606 new=2174 sentences=7 missing=0,get_pg_wait_stats [Darling] old=1483 new=2164 sentences=6 missing=0,get_pg_index_usage [Darling] old=1471 new=2244 sentences=7 missing=0.There is no rig in this lane.
PerformanceMonitor.Common, so per the lanebrief the Lite suite is not required.
**Q:**/**Correct:**/**Prevents it:**D9 labels, which are the brief's stated exception. 0 other hits.Coordinator verification
paramcheck.py): theget_pg_index_usagelimit text is all in the tail. Forget_pg_wait_stats, the phrase "whatever this is set to" left thelimitparameter. The tail has it in a new sentence:Raising or lowering limit does not change that math - the shares stay of the whole window whatever this is set to.The meaning is kept, so I accepted it.total_wait_time_ms,returned_wait_time_ms) androot_backend_id.limit, and the shares are of the whole window. The question's word "missed" can be read either way.4087-reported.txtmatches the table.Handoff
None. All three assigned tools converted and green before the deadline.
Generated with Claude Code
https://claude.ai/code/session_01Kukh6S1arS5ffv7fcEhddd