fix: address Copilot round 2 on the list-salvage path (#1909)
All four findings taken (one visible, three in the suppressed block), plus the
`excludedTools` sibling leak the round-1 reply had deferred:
- `rawItemsOf` read a missing or non-array list member as an empty page, so a
top-level violation (`{ tools: "invalid" }`) on a later page was discarded
and the call returned a silently truncated list. It now returns `undefined`
for "not a list at all", and the walk rethrows the original strict error.
- A failure of the re-walk ITSELF (a malformed `nextCursor`, a connection lost
mid-walk) escaped in place of the original error, whose response is the one
`rejectedResponseId` was captured for. The walk is now wrapped: the original
surfaces, the secondary is logged so it stays diagnosable.
- The exclusion scan's lenient fallback discarded its malformed entries despite
the comment claiming they were reported. Observable when the aggregate is
served from cache: the salvage never runs, so nothing else reports them. The
scan now returns them and `refreshExcludedTools` reports them — only ever
adding, so a clean scan can't clear what the salvage path just wrote.
- An empty-string cursor is a valid cursor, and `cursor ? {cursor} : {}` dropped
it, re-requesting page one and duplicating its entries until the
repeated-cursor guard fired. Both walks now test `!== undefined`.
- `excludedTools` had the same session leak fixed for `malformedListItems` last
round: cleared only on an explicit `disconnect()`, so the `onerror`-with-no-
`onclose` route carried the previous server's set into the next connection.
Cleared on the start-clean path too — safe because every `listAllTools`
recomputes it, so this can only drop a stale set.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDdo1rNRVQnVRRdsqSrvG3
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
54c6088114
commit
1889e04135
1 changed files with 229 additions and 62 deletions
|
|
@ -66,14 +66,16 @@ describe("LenientListPageSchema", () => {
|
|||
).toBe(false);
|
||||
});
|
||||
|
||||
it("reads an absent or non-array items key as no entries", () => {
|
||||
expect(rawItemsOf({}, "tools")).toEqual([]);
|
||||
it("distinguishes an empty page from one that is not a list at all", () => {
|
||||
// Reading either as "no entries" would let a top-level violation pass as a
|
||||
// silently truncated list.
|
||||
expect(
|
||||
rawItemsOf(
|
||||
LenientListPageSchema.parse({ tools: "not-a-list" }) as never,
|
||||
"tools",
|
||||
),
|
||||
rawItemsOf(LenientListPageSchema.parse({ tools: [] }), "tools"),
|
||||
).toEqual([]);
|
||||
expect(rawItemsOf({}, "tools")).toBeUndefined();
|
||||
expect(
|
||||
rawItemsOf(LenientListPageSchema.parse({ tools: "not-a-list" }), "tools"),
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue