Skip to content

v1.0.0: cherry-pick the #5097 roots refresh ordering fix onto the release branch - #5099

Merged
cliffhall merged 6 commits into
v2/release/1.0.0from
v2/fix/5097-release-1.0.0-cherry-pick
Oct 10, 2026
Merged

cliffhall merged 6 commits into
v2/release/1.0.0from
v2/fix/5097-release-1.0.0-cherry-pick

Conversation

@cliffhall

@cliffhall cliffhall commented Oct 10, 2026 •

Copy link
Copy Markdown
Member

Closes #5097

Targets the one-time release branch v2/release/1.0.0 (retargeted automatically after #5096 merged). The v2/main fix is #5098; this branch carries the same fix for the SDK 1.x release.

Description

Cherry-picks the #5097 fix from #5098 (git cherry-pick -x), so overlapping roots refreshes cannot apply out of order in v1.0.0:

  1. a13e809d (from 423fe6b7): each roots refresh (the initial load and every roots/list_changed) takes a generation before it asks for roots; its answer is applied only if no newer refresh has started since, checked after both roots/list and validation. A stale answer is logged and dropped, so it cannot undo a newer update such as a revocation (filesystem: a roots update with no valid roots keeps the previous allowed directories #5094).
  2. be9a5551: records it the release branch's way, removing the changeset and adding its text to src/filesystem/CHANGELOG.md under the unpublished 1.0.0. No version change.
  3. b503c696 (from 564e84d6, Copilot on fix(filesystem): apply only the newest roots refresh's answer #5098): while the initial roots load is pending, a refresh that overtakes it replaces the initialization gate, so tool calls waiting for the initial roots wait for the refresh that will actually apply instead of running against the command-line directories.
  4. 0f8bfe3b: adapts the new failure test to SDK 1.x, where a client handler's error reaches the server as MCP error -32603: gone (as the existing re-fetch test on this branch already expects).
  5. c88c3bff (from 11394a40, Copilot on fix(filesystem): apply only the newest roots refresh's answer #5098): the failed-refresh test asserts that waiting calls are released before the held initial answer arrives.

Server Details

  • Server: filesystem
  • Changes to: roots refresh ordering and the initialization gate (server.ts), CHANGELOG.md (1.0.0 entry), tests

Motivation and Context

#5097, raised by Copilot on #5095 and #5096; shipped in 1.0.0 by maintainer decision on #5080.

How Has This Been Tested?

Four new protocol-level tests in server-roots.test.ts, on this SDK 1.x tree:

  • two overlapping updates answered out of order (the older one held back): the newer revocation stands and the stale answer is logged;
  • an update that overtakes a held initial load: the update wins;
  • a tool call started after the stale initial answer is released but before the overtaking answer arrives stays pending, then is refused once the newer roots apply (fails with the gate handoff disabled);
  • a failing overtaking refresh releases waiting calls, checked while the initial answer is still held.

Filesystem: 334 passed / 2 skipped. npm run local:gate: EXIT=0. The out-of-order client evidence (an older roots/list answer held 800 ms behind a newer revocation) is in #5098, and is re-run against the release tree for the ledger before #5090 merges.

Breaking Changes

None.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)

Checklist

  • I have read the MCP Protocol Documentation (Roots)
  • My changes follow MCP security best practices
  • I have updated the server's README accordingly (not applicable)
  • I have added a changeset (folded into the 1.0.0 CHANGELOG entry, as on this branch)
  • I have tested this with an LLM client (not done; see fix(filesystem): apply only the newest roots refresh's answer #5098)
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have documented all environment variables and configuration options (not applicable)

🤖 Generated with Claude Code

cliffhall and others added 2 commits October 10, 2026 03:10
Each roots refresh (the initial load and every roots/list_changed) takes a generation before it asks for roots; its answer is applied only if no newer refresh has started, checked after both roots/list and validation. A stale answer is logged and discarded, so it cannot undo a newer update such as a revocation.

Closes #5097

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
(cherry picked from commit 423fe6b)
Signed-off-by: cliffhall <cliff@futurescale.com>
As with #5094 on this branch: 1.0.0 is unpublished and its changesets are already folded, so the cherry-picked fix is recorded in that entry. No version change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
@changeset-bot

changeset-bot Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: ee1ad25

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The implementation matches #5097, includes focused regression coverage, and passes the relevant validation and coverage checks.

0 open findings

What changed in this PR

Backports the #5097 filesystem Roots ordering fix onto the v1.0.0 release branch.

Changes:

  • Discards stale results from overlapping Roots refreshes.
  • Adds protocol-level race regression tests.
  • Records the fix in the 1.0.0 changelog.
File Description
src/​filesystem/​server.ts Adds generation-based refresh ordering.
src/​filesystem/​__tests__/​server-roots.test.ts Tests overlapping updates and initial-load overtaking.
src/​filesystem/​CHANGELOG.md Documents the release fix.

🧠 Review effort: Balanced


💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Base automatically changed from v2/fix/5094-release-1.0.0-cherry-pick to v2/release/1.0.0 October 10, 2026 07:17
cliffhall and others added 2 commits October 10, 2026 03:19
… the initial roots

When a roots/list_changed overtakes the initial roots/list, the initial refresh's stale answer is discarded, but it used to release the tool calls waiting on the initial load, which then ran against the command-line directories before the newer roots applied. The overtaking refresh now replaces the initialization gate while the initial load is pending, and waiting calls follow it until it settles (Copilot on #5098).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
(cherry picked from commit 564e84d)
Signed-off-by: cliffhall <cliff@futurescale.com>
 test

The cherry-pick of 564e84d comes from the SDK v2 tree, where a client handler's error reaches the server as "gone"; SDK 1.x wraps it as "MCP error -32603: gone", as the existing re-fetch test on this branch already expects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
@cliffhall

Copy link
Copy Markdown
Member Author

Copilot round 1: clean. Since then, b503c69 cherry-picks the gate fix from #5098 round 1 (waiting tool calls follow the refresh that overtook the initial load), and 0f8bfe3 adapts its failure test to SDK 1.x (MCP error -32603: gone). Filesystem 334 passed; gate EXIT=0. Requesting round 2 for the new commits.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The PR description and test evidence do not reflect the final four-commit implementation.

1 open finding

🧠 Review effort: Balanced

Comment thread src/filesystem/server.ts
…l answer arrives

The failed-overtaking-refresh test released the held initial roots/list before asserting, so it would also pass if callers stayed blocked on it. Assert first, then release for cleanup (Copilot on #5098).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
@cliffhall

Copy link
Copy Markdown
Member Author

Copilot round 2: 1 finding (low), the PR body described the first version; updated to the five-commit branch and its four tests. Also c88c3bf cherry-picks the test fix from #5098 round 2. Gate EXIT=0. Requesting round 3.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

One regression test leaves asynchronous Roots processing racing with teardown.

1 open finding
1 resolved since last review

🧠 Review effort: Balanced

Comment thread src/filesystem/__tests__/server-roots.test.ts
The failed-overtaking-refresh test returned right after releasing the held initial roots/list, so its answer handling and root validation could race afterEach's teardown. Wait for the stale answer to be discarded first (Copilot on #5099).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
cliffhall added a commit that referenced this pull request Oct 10, 2026
The failed-overtaking-refresh test returned right after releasing the held initial roots/list, so its answer handling and root validation could race afterEach's teardown. Wait for the stale answer to be discarded first (Copilot on #5099).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
@cliffhall

Copy link
Copy Markdown
Member Author

Copilot round 3: 1 finding (medium), fixed in ee1ad25 (the test waits for the stale initial answer before teardown). Gate EXIT=0. Requesting round 4.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The initialization promise retry loop’s key race remains untested.

0 open findings

1 resolved since last review
Previously missed (1)

In code that hasn't changed since last review

Medium severity Test misses retry behavior during concurrent initialization refresh

src/​filesystem/​__tests__/​server-roots.test.ts:463

This call starts only after the stale initial promise has resolved and initialization already points at the newer refresh, so it does not exercise the new retry loop at server.ts:213-216. A regression that removes that loop would still pass. Start the tool call while it is awaiting the original initialization promise, then start the refresh, release the initial answer, and assert the call remains pending until the refresh completes.

🧠 Review effort: Balanced

@cliffhall

Copy link
Copy Markdown
Member Author

Copilot round 4: 0 findings. Loop ends on the first clean round. Merging.

@cliffhall
cliffhall merged commit 29db105 into v2/release/1.0.0 Oct 10, 2026
33 checks passed
@cliffhall
cliffhall deleted the v2/fix/5097-release-1.0.0-cherry-pick branch October 10, 2026 08:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

filesystem: overlapping roots refreshes can apply out of order and undo a revocation

2 participants