Repository navigation
get_pg_log_events/get_pg_index_bloat/get_pg_deadlocks move reading guidance off tools/list (#3898) - #4068
Merged
Conversation
…idance off tools/list (#3898) Head/tail split for the pgA family (Darling only, no Lite twins). Heads carry the gating, empty/null and sighting-count guardrails; full original prose moves to each tool's get_tool_guide tail. D2: min_severity, get_pg_index_bloat.limit/answered_only and get_pg_deadlocks.limit were over the 200-char parameter cap, trimmed to a guardrail sentence with the overflow moved verbatim into the tool's tail. Re-points McpPageContractTests' page-contract pin (the #3679 "This is what bounds the page" / truncated wording) to read the tail when the tool is #3898-converted, since the wording moved off the now-shortened limit parameter for get_pg_deadlocks and get_pg_index_bloat. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M7r1CXmEcBmsrTV34CCLfF
erikdarlingdata
marked this pull request as ready for review
September 23, 2026 20:21
erikdarlingdata
enabled auto-merge (squash)
September 23, 2026 20:21
7 of 8 tasks
erikdarlingdata
added a commit
that referenced
this pull request
Sep 23, 2026
…ntries in their sections (#4080) Adds 42 entries and 42 link refs (#3992, #3995, #3996, #3998, #4001, #4002, #4003, #4007, #4010, #4011, #4013, #4015, #4020, #4022, #4025, #4029, #4030, #4031, #4036, #4038, #4039, #4040, #4044, #4047, #4048, #4049, #4050, #4051, #4055, #4061, #4063, #4064, #4065, #4066, #4067, #4068, #4069, #4070, #4071, #4073, #4074, #4078). Each PR's entry was buffered, and this lands every entry whose PR was merged on origin/dev when it ran. #3989 left 26 entries under bare 'Changed' and 'Fixed' lines above '### Added'. They move into '### Changed' and '### Fixed', below the new entries, and one blank line stays under [Unreleased]. Claude-Session: https://claude.ai/code/session_01Ua31ugERL5DmhFVRtf6keQ Co-authored-by: Claude Opus 5.5 <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
A client loading Darling's whole MCP catalog pays for every tool description before its first question. These
three tools were among the largest descriptions on the wire (4,199 / 3,491 / 2,839 served characters). The
guidance in them is not deleted, it moves off
tools/listinto each tool'sget_tool_guidetail, which aclient reads on demand.
What changes
Converted, Darling only (none of the three has a Lite twin):
get_pg_log_events(DarlingMcpPgLogEventTools.cs)get_pg_index_bloat(DarlingMcpPgIndexTools.cs)get_pg_deadlocks(DarlingMcpPgDeadlockTools.cs)Each head states the guardrail facts a model needs to choose the tool and not misread an empty or ambiguous
answer: per-family gating and the two silent whole-tool preconditions for
get_pg_log_events; estimated vs.measured, the skipped-reason semantics and the answerless-rows-sort-first trap for
get_pg_index_bloat; andthe "empty does not mean none occurred" / transport-dependent
times_seentraps forget_pg_deadlocks. Everysentence of the original text is preserved verbatim in the tail (dropcheck confirms below); order was not
changed.
D2 (over-cap parameters). Four parameters were over the 200-char cap for a converted tool. Each was trimmed
to its guardrail sentence plus "See the tool's reading guide."; the removed text moved, verbatim or with a
pronoun made explicit for standalone reading, into the tool's own tail:
get_pg_log_events.min_severity: 233 -> 147 chars. Moved: "WARNING is the 'errors only' setting; connectionand lock_wait lines are LOG and are excluded by anything above it."
get_pg_index_bloat.limit: 285 -> 65 chars. Moved: "This is what bounds the page - read truncated to knowwhether the census held more indexes than were returned; it is observed by fetching one row past this cap,
never inferred from a full page, and the coverage field is measured independently of it."
get_pg_index_bloat.answered_only: 563 -> 129 chars. Moved (this parameter's Description was built fromconcatenated string literals; the whole concatenation was replaced with one literal): "answered_only defaults
to false, which leaves the answerless-first order alone. Set answered_only when you want the RANKING:
answerless rows sort first by design, so on a server whose answerless population exceeds the 1000-row maximum
the answers cannot be reached at any limit - measured at 1,779 answerless ahead of 726 answers covering 213.8
GB. The coverage census is unaffected by this, so a filtered ranking still carries the same denominator; read
the reach field to see which situation you are in."
get_pg_deadlocks.limit: 245 -> 70 chars. Moved: "This is what bounds the page - read truncated to knowwhether the window held more distinct deadlocks than were returned; it is observed by fetching one row past
this cap, never inferred from a full page."
Topics. No prose repeats verbatim across these three tools (each is a different domain: log events, index
bloat, deadlocks), so
McpToolGuideTopics.PgA.cs'ss_pgAarray is untouched (still empty).Before / after (served characters, Darling only — no Lite twins)
get_pg_log_eventsget_pg_index_bloatget_pg_deadlocksCorrections
None. All three original descriptions were re-checked against the tool bodies while drafting the heads; no
factual errors found.
D4 removals
None. None of the three tools' wire text (head or tail) carried an issue reference, an anecdote, or a WIRE
CHANGE notice.
Re-pointed pins
McpPageContractTests.EveryPgPagedToolThroughTheHelper_FetchesOnePastTheCap_AndObservesThroughBoundPage: itslimitparameter pin (the get_pg_server_config counted the page and called it the server: non_default_count is now the snapshot's, computed above the cap, truncated is observed off a limit + 1 fetch, and its three sibling pages stop inferring truncation from a full page (#3653 A3 residue) #3679 "This is what bounds the page" /truncatedwording) failed forget_pg_deadlocksandget_pg_index_bloatonce theirlimitparameters were trimmed under D2. Re-pointed toread the tool's tail when the tool carries the
<<GUIDE>>marker, falling back to the parameter descriptionfor every other (unconverted) tool in that roster. Reason: the wording is still on the wire, just moved from
the parameter to the tail, which is exactly what D2 asks for.
McpDescriptionTruthPinTests,McpZeroIsAMeasurementTests,McpPayloadContractCensusTests,PgCappedReadSurfaceTests,PgIndexBloatCoverageTests,PgIndexBloatGridReachTests,PgDeadlockNormalizationTests,PgLogEventsPipelineTests,PgLoggingCollectorOffTests,DarlingMcpPgLoggingAuditToolsTests,PgLogEventMetricsTests) neededre-pointing: each either reads the raw (unsplit)
Descriptionattribute directly (so head+tail both stillsatisfy a substring check), or pins something unrelated to these three tools' description text (SQL text,
registration census, a different tool's description, or an unrelated
Descriptionproperty on an analysisfinding).
D9: known traps for the misreading check
get_pg_log_eventswithfamily=connectioncame back empty: no connections happened?Correct:
log_connections/log_disconnectionsmay simply be off on the target; the tool does not auditthat.
Prevents it: the head's "Each family is gated by its own logging setting; an off family is EMPTY here,
unaudited."
get_pg_log_eventscame back empty at once: the server is quiet?Correct: could also be a non-UTC
log_timezone(the whole log refused) or a non-Englishlc_messages(nothing matches).
Prevents it: the head's "Also needs log_timezone = UTC (else refused whole) and English lc_messages
(else empty) - an all-family empty can mean either, not 'no events'."
min_severity=WARNINGonget_pg_log_events: does that just filter out low-priority noise the sameway
log_min_messages=WARNINGwould?Correct: no, the two orderings disagree (a
lock_waitLOG-level line and a suppression-order LOG are notthe same rank).
Prevents it: the head's "min_severity ranks by SERIOUSNESS, not log_min_messages' order."
get_pg_log_eventsshowstimes_seen: 1on an RDS target for a lock_wait entry: was it captured justonce and might there be more updates coming?
Correct: 1 is the normal steady state on RDS/Aurora (consume-once log API); it never climbs the way a
self-hosted re-read does.
Prevents it: the head's "times_seen is a sighting count, not an occurrence count." plus the tail's
transport-specific detail.
get_pg_index_bloatrows showest_bloat_pctbut notest_tuple_bytes— no wait,est_tuple_bytesis populated: does that mean the row has a usable bloat estimate?
Correct: no;
est_tuple_bytesis populated even on 100% of skipped (answerless) rows. Onlyskipped_reason IS NULLmeans there is an answer.Prevents it: the head's "skipped_reason present = NO answer, not healthy" plus the tail's trust-predicate
detail (kept verbatim, not in the head due to the 620 cap).
get_pg_index_bloatwith the defaultlimitreturns 25 rows, all with askipped_reason: the serverhas no reclaimable bloat?
Correct: answerless rows sort first by design; raising the limit does not reach the answers on a big
server.
answered_only=trueis the only route to the ranking.Prevents it: the head's "Answerless rows sort FIRST by design: read coverage before judging reach, and
pass answered_only=true to reach real answers."
get_pg_index_bloat'smeasurement_kindis "measured" on a row: was that walked just now?Correct: no; "measured" rows are an OLDER
pgstatindexreading still inside the retention window, not afresh walk.
Prevents it: the head's "measurement_kind: estimated (stats model) or measured (an older pgstatindex
reading in retention)."
get_pg_index_bloatranks a 64 kB index above a 10 GB index: is that a bug?Correct: no, ranking is by reclaimable BYTES, never percentage, by design.
Prevents it: the head's "ranked by reclaimable BYTES, never percentage."
get_pg_deadlocksreturns empty on a target known to have contention: the server had no deadlocks?Correct: not necessarily;
log_error_verbosity=terseor a non-Englishlc_messagesboth produce thesame empty result as a quiet server.
Prevents it: the head's "An empty answer does not mean none occurred: log_error_verbosity=terse drops the
DETAIL field this reads, and a non-English lc_messages ... matches nothing - both look like a quiet server."
Correct:
get_pg_database_stats' cumulative counter, an independent source that doesn't depend on logsettings.
Prevents it: the head's "Check both, or get_pg_database_stats' counter, first."
get_pg_deadlocksshowstimes_seen: 1on an RDS target: was this deadlock seen only once so far,with more sightings likely?
Correct: 1 is the ordinary steady state on RDS/Aurora, not a partial count; it is not the same signal as
a climbing value on a self-hosted target.
Prevents it: the head's "times_seen is a sighting count, not a deadlock count: it depends on transport -
self-hosted climbs on re-reads, RDS/Aurora normally stays 1 (normal there, not partial)."
get_pg_deadlocksreturned 25 rows (the default limit) withtruncatedunset in the visible payloadfields listed in the head: was that the whole window?
Correct: unknown from the head alone; the tail's moved parameter text says
truncatedis observed by anover-fetch of one row past the cap, never inferred from a full page — read the tail or the payload's
truncatedfield, don't assume a full page means more exist.Prevents it: the tail's moved D2 text: "read truncated to know whether the window held more distinct
deadlocks than were returned; it is observed by fetching one row past this cap, never inferred from a full
page."
get_pg_index_bloatwithanswered_only=truereturnsstatus: no_answers: is that the same as anempty page with the default (answerless-first) ordering?
Correct: no;
no_answersmeans nothing on the server is measurable at all, a stronger and differentstatement than "the page you asked for happened to be empty."
Prevents it: the tail's "answered_only over a server where NOTHING has an answer returns status
no_answers rather than an empty" (kept verbatim in the tail; not in the head due to the 620-char cap, listed
here as a residual trap for the coordinator's awareness).
Test plan
dotnet build Darling/Darling.Tests/Darling.Tests.csproj -c Debug: 0 Warning(s), 0 Error(s).McpToolsListBudgetTests,McpToolGuideTests,McpToolGuideHeadsPgATests(new),McpPageContractTests, plus every other test class found bygrep -rl -E "get_pg_log_events|get_pg_index_bloat|get_pg_deadlocks" Darling/Darling.Testsnarrowed to theones reading
Description/McpServerTool(22 classes examined, 458 tests run) — all green except the onere-pointed pin above, now green after the fix.
get_pg_index_bloat's head below itsHeadFactsguardrail phrase,confirmed
McpToolGuideHeadsPgATestsfailed, restored.python dropcheck.py origin/dev HEAD get_pg_log_events get_pg_index_bloat get_pg_deadlocks: all threemissing=0.origin/dev(d7b8aa0): dropcheck showsmissing=0for all three tools. D9: 13/13, with no misreading. Five answers were hedged, and the model marked get_tool_guide as needed. FullDarling.Tests13,222 total, 0 failed. FullLite.Tests5,232 total, 0 failed.Handoff
All three assigned tools (
get_pg_log_events,get_pg_index_bloat,get_pg_deadlocks) are fully converted andgreen on every targeted test. Nothing was left unconverted. Outstanding before merge:
git merge origin/dev,rebuild, and one full Darling suite run (not done in-lane due to the context watchdog); the plain-English
checker against this PR body and the CHANGELOG entry.
CHANGELOG entry written to
changelog-buffer/changelog_entries/<PR>-reported.txtper the brief (SECTION: Changed).🤖 Generated with Claude Code
https://claude.ai/code/session_01M7r1CXmEcBmsrTV34CCLfF