goose emits a roots notification that 2026-07-28 removed, and rmcp does not stop it

Kind: idiosyncrasy · Status: current · Last verified: 2026-08-24 · Clients: goose, openai-codex · Revisions: 2026-07-28

notifications/roots/list_changed was removed in 2026-07-28 under SEP-2575. goose still sends it, and by default negotiates 2026-07-28.

The call site has no version check:

async fn do_update_working_dir(&self, new_dir: PathBuf) -> Result<(), Error> {
    let client = self.client.lock().await;
    let shared = client.service().shared_working_dir();
    *shared.write().await = new_dir;
    client.peer().notify_roots_list_changed().await?;
    Ok(())
}

The default lifecycle prefers the revision that removed it:

ClientLifecycleMode::Auto {
    preferred_versions: vec![ProtocolVersion::V_2026_07_28],
    legacy_version: Some(ProtocolVersion::LATEST),
}

The SDK does not filter it

goose declares rmcp = "3.0.0"; the lockfile resolves 3.1.4, which is what was read.

Peer::notify_roots_list_changed is generated by the method! macro's peer_not arm. That arm constructs the notification and sends it, with no version parameter in scope:

($(#[$meta:meta])* peer_not $method:ident $Not:ident) => {
    pub async fn $method(&self) -> Result<(), ServiceError> {
        self.send_notification(ClientNotification::$Not($Not {
            method: Default::default(),
            extensions: Default::default(),
        }))
        .await?;
        Ok(())
    }
};

send_notification pushes onto the peer sink channel and awaits the responder. It reads no protocol version.

Every V_2026_07_28 comparison in rmcp 3.1.4 sits somewhere else: server-side request handling (handler/server.rs), _meta key requirements (model/meta.rs), and lifecycle and version negotiation (service/client.rs:278,313). None is on the outbound client notification path. uses_legacy_lifecycle looks like a candidate but only selects initialize-versus-discover, and its callers are the server handler and the streamable HTTP server transport.

RootsListChangedNotification also remains a first-class type in the 3.1.4 model, undeprecated and unbounded by version.

So the notification reaches the wire. A server that negotiated 2026-07-28 with goose receives notifications/roots/list_changed whenever the session changes working directory, for a method that revision does not define.

Codex does not hit it

Codex CLI is the other Rust client on rmcp, at 3.1.3 rather than 3.1.4, and it is unaffected — because it does not implement roots at all. Its ClientHandler has no list_roots, roots is absent from its declared capabilities, and notify_roots_list_changed is never called. There is no working-directory change that could reach the notification path.

So the gap needs a caller, not just the SDK. A Rust client is exposed only once it opts into roots, which makes goose the case to watch rather than the first of many.

Scope

This is not goose-specific. rmcp is the official Rust SDK, and any Rust client that calls notify_roots_list_changed on a 2026-07-28 connection gets the same result. The SDK leaves version-appropriateness to the caller and no caller has a reason to expect that.

Practical impact is currently small, since roots is itself deprecated as of the same revision and few servers act on the notification. It matters more as a shape: the SDK exposes removed methods without guardrails, so the same pattern will recur for anything else 2026-07-28 dropped.

Sources