Skip to content

MCP budget: size get_object_locking's default rows (#4198) - #4258

Merged
erikdarlingdata merged 7 commits into
devfrom
fix/4198-object-locking-default
Sep 25, 2026
Merged

erikdarlingdata merged 7 commits into
devfrom
fix/4198-object-locking-default

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

Part of #4198.

Why

get_object_locking (Darling and Lite) had a hardcoded 200-row cap. It had no override and no truncation
signal. Field measurement on a busy production store: 71,332 bytes at default arguments, 460 ms. That is more
than double McpResponseBudget.DefaultBytes (32 KB). The issue's ruling applies: size each tool's defaults
from its own measured bytes per row, and keep the envelope honest with truncated/*_returned. An explicit
argument still gets what it asks for.

This tool has no long free-text field to preview or truncate, unlike query text or deadlock XML. Every field is
a short name or a number. So the fix is entirely a row-count default, the same shape get_index_usage already
uses in this file.

What changes

  • Both get_object_locking bodies (Darling's DarlingMcpObjectStatsTools.cs, Lite's McpObjectStatsTools.cs)
    take a new optional limit parameter (int, default 75, validated with McpHelpers.ValidateTop).
  • The reader is called with limit + 1. The result is bound with McpHelpers.BoundPage, the same
    over-fetch-by-one pattern several other Darling tools already use. truncated is OBSERVED, not inferred.
  • The envelope gains objects_returned, truncated, and a note field, mirroring get_index_usage's wording
    style. Rows are still ordered by total wait time descending, so a truncated page is the highest-contention
    indexes, not an arbitrary cut.
  • No SQL text changed on either product. GetIndexLockingAsync/LocalDataService.GetIndexLockingAsync already
    took a row-count parameter. Only the value passed to it, and the parameter/envelope around it, changed. I
    deliberately left DarlingObjectStatsReader.DatabaseSizeLatestSql and get_index_usage untouched: both are
    other lanes' territory tonight per my brief.
  • Darling.Tests/DarlingMcpObjectStatsToolsTests.cs: get_object_locking moved off the
    ParamContract_ServerNameOnly_Optional theory, since it is no longer server-name-only, into its own
    ObjectLocking_TakesALimit_Optional fact, mirroring the existing get_index_usage one. This was the one
    failure the full suite caught (a param-contract pin, not a defect), and it is fixed here.
  • McpToolsListBudgetTests.cs (both products): the new limit parameter added 79 bytes to tools/list. The
    per-tool budget file gained param get_object_locking.limit 35, and each TotalCeilingBytes moved by
    exactly that measured amount, with a change-log line.
  • New test files, one per product, not shared. Several MCP read tools have no default response-size budget: at default arguments 12 tools return >50 KB and 3 return >100 KB for one server, more than an agent client's per-result cap #4198 lanes ran tonight, so files are separate.
    Darling/Darling.Tests/ObjectLockingBudgetLiveTests.cs (live Postgres, gated on DARLING_TEST_PG) and
    Lite.Tests/ObjectLockingBudgetTests.cs (local DuckDB, no gate needed). Both seed 200 rows across 10
    databases. Every row carries contention, so all 200 survive the read's filter. Both assert the default
    call is under budget and marked truncated. An explicit limit covering every seeded row returns
    everything with truncated: false.
  • The web viewer's /api/read row for get_object_locking in DarlingWebEndpoints.cs now pins its own
    limit at 200. That is the old effective ceiling (a hardcoded fetch with no override), not the new MCP
    default of 75. It is the same fix, and the same value, the neighboring get_index_usage row already
    carries for an unrelated earlier default cut. 200 stays well under both McpHelpers.MaxTop and the
    dispatch layer's own MaxRowLimit (1000 each). The matching /api/catalog row gained a limit parameter
    declaration. The viewer's server-tabs.js panel passes only { server }, no limit, so the row's
    default governs. A new pin, ObjectLockingWebDefaultTests, reads the row's own lines and fails if either
    default stops being 200. Lite parity: confirmed by grep that no Lite UI code calls
    McpObjectStatsTools.GetObjectLocking directly. Lite's UI reads LocalDataService, not the MCP tools, so
    this default change does not reach Lite's UI at all.
  • This branch conflicted with origin/dev. Merged, not rebased, to pick up the concurrent
    fix/4192-mcp-slow-tools lane. That lane had separately cut get_index_usage's own MCP default
    (IndexUsageTop) from 200 to 75. Kept both constants at their own new value. Re-measured
    McpToolsListBudgetTests.TotalCeilingBytes with both changes applied, rather than adding the two PRs'
    deltas by hand: 171,797 bytes.

Test plan

Measured on my own seeded fixture: 200 rows across 10 databases, index/table names sized to reproduce the
field's ~356.7 bytes/row.

  • Before (pristine tool, hardcoded 200-row default, no limit param): 75,496 bytes, close to the field's
    71,332 B, confirming the fixture reproduces the reported shape. The test fails here, as expected, proving the
    pin catches the old shape.
  • After (default limit = 75): 28,801 bytes, under the 32,768-byte budget with about 12% headroom for
    index/table names longer than this fixture's.
  • Darling.Tests, targeted classes while iterating (ObjectLockingBudgetLiveTests,
    DarlingMcpObjectStatsToolsTests, DarlingMcpObjectStatsToolsSurfaceAndSqlTests,
    McpToolsListBudgetTests, IndexUsageTruncationTests*), all green.
  • Darling.Tests full suite, after merging origin/dev: 13,844 total, 0 errors, 0 failed, 47
    skipped (unrelated gated/live-only tests), 1 not run
    . One param-contract failure on the first
    run was fixed before this run.
  • Lite.Tests full suite, run once: 5,340 total, 0 errors, 0 failed.
  • Lite and Darling.Tests/Lite.Tests builds: 0 warnings each.
  • Did not cross-check against dev's own CI. No pre-existing failures came up, so there was nothing to
    cross-check.
  • Web-viewer fix (this update): Darling.Tests, filtered to every class that reads
    DarlingWebEndpoints.cs (55 classes, including DarlingWebEndpointsTests and ServerPageTabsTests).
    Also filtered to ObjectLockingWebDefaultTests, McpToolsListBudgetTests and
    McpPayloadContractCensusTests. Result: 1,434 total, 0 failed, 31 skipped (live classes, no rig for this
    update). Also ran DarlingMcpObjectStatsToolsTests alongside the budget and census classes: 73 total, 0
    failed. Proved the new pin fails by reverting the limit argument alone: 1 run, 1 failed,
    Assert.Contains on the missing substring. Restored the fix and reran the full filtered set green.
  • Live suite: CI decides.

CHANGELOG entry

SECTION: Fixed
ENTRY:

Generated with Claude Code

https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ

…et (#4198)

get_object_locking had a hardcoded 200-row cap with no override and no
truncation signal: 71,332 bytes at default arguments on a busy
production store, more than double McpResponseBudget.DefaultBytes.
Both SKUs now take an optional limit (default 75, over-fetched by one
and bound through McpHelpers.BoundPage the way get_index_usage
already does), with truncated/objects_returned/note added to the
envelope so a short answer is never mistaken for a complete one. An
explicit limit still gets what it asks for, up to McpHelpers.MaxTop.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
@erikdarlingdata erikdarlingdata changed the title DO NOT MERGE (MCP budget): size get_object_locking's default rows (#4198) MCP budget: size get_object_locking's default rows (#4198) Sep 25, 2026
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review September 25, 2026 07:19
@erikdarlingdata
erikdarlingdata enabled auto-merge (squash) September 25, 2026 07:20
erikdarlingdata and others added 4 commits September 25, 2026 03:50
# Conflicts:
#	Darling/Darling.Tests/McpToolsListBudgetTests.cs
#	Darling/PerformanceMonitor.Darling.Service/Mcp/DarlingMcpObjectStatsTools.cs
… dev (#4258)

get_object_locking's new limit MCP default (75) replaces the old hardcoded
200-row fetch that had no override. The web viewer's /api/read row calls
the same MCP method, so without its own default it would silently drop
from 200 rows to 75. Pin the row's limit to 200, the same value the
neighboring get_index_usage row already pins for the identical reason, and
add a source-text pin so a future edit that drops the argument fails a
test instead of silently shrinking the page the viewer renders.

Also resolves the origin/dev merge conflict this branch had: dev had
already lowered get_index_usage's own default (IndexUsageTop) from 200 to
75 in a separate PR, while this branch lowered ObjectLockingTop the same
way. Kept both constants at their own new value, and re-measured
McpToolsListBudgetTests.TotalCeilingBytes with both changes applied
(171,797 bytes) rather than adding the two PRs' deltas by hand.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
All three deltas now in: audit_config +82, deadlock_detail +364,
object_locking +79 (Darling); audit_config +0, deadlock_detail +253,
object_locking +79 (Lite). No new conflicts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ
Incorporates plan_corrections (+137 Darling/Lite) from dev (#4257).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ
@erikdarlingdata
erikdarlingdata enabled auto-merge (squash) September 25, 2026 09:30
erikdarlingdata and others added 2 commits September 25, 2026 06:12
Darling: HEAD=172,298 (object_locking +79) + dev=172,367 -> 172,446
Lite: HEAD=90,350 (object_locking +79) + dev=90,418 -> 90,497

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ
@erikdarlingdata
erikdarlingdata merged commit 4995e8b into dev Sep 25, 2026
15 of 16 checks passed
@erikdarlingdata
erikdarlingdata deleted the fix/4198-object-locking-default branch September 25, 2026 10:56
erikdarlingdata added a commit that referenced this pull request Sep 25, 2026
Measured with McpToolsListBudgetTests after merging origin/dev (#4261,
#4258, #4265, #4267, #4264, #4266 and #4268) plus this PR's catalog
changes. Budget, census and tool-guide classes: 219/219.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ
erikdarlingdata added a commit that referenced this pull request Sep 25, 2026
…nder the response budget (#4198) (#4272)

* MCP budget: describe_custom_view_catalog default groups measures by source (#4198)

Default default-argument call was 98,173 bytes (#4198's own measurement), three
times the tool's 32 KB budget. It is pure static reference data (no server/store
read), so the cut groups the 179 measures by source and keeps only
key/displayName/kind/unitFamily/validAggregates per measure; source=<name>
drills into one source's full detail, full_detail=true returns the original
shape unfiltered. /api/catalog (the web Custom Views editor) calls the
underlying builder directly, never this MCP method, so it is unaffected -
pinned in DarlingComposeTests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3

* Trigger CI (draft PR skipped the Build workflow)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ

* Add comment to DarlingMcpCustomViewCatalogSizeTests.cs to trigger Build CI

GitHub did not fire pull_request events for this draft-opened PR.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ

* fix(#4272): reword validAggregates description - compact drops allowedDimensions, source=<name> returns it

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3

* #4272: set TotalCeilingBytes to the measured 174,236 after merging dev

Measured with McpToolsListBudgetTests after merging origin/dev (#4261,
#4258, #4265, #4267, #4264, #4266 and #4268) plus this PR's catalog
changes. Budget, census and tool-guide classes: 219/219.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVjn4PBJN71NQXdFo6ZxNQ

---------

Co-authored-by: Claude Sonnet 5 <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.

1 participant