Prevent shared instruction providers from delegating to themselves (#50093)

## Why

Reloading an unloaded root from a surviving child can supply the shared instruction wrapper already registered for that root. Replacing its underlying provider with that same wrapper makes instruction loading recurse into itself.

## What changed

Reuse the existing wrapper without replacing its provider when both are the same `Arc`. Continue to accept distinct replacement providers.

## Testing

Extend regression coverage to reload the root from a surviving child and verify that shared instructions still update. Check wrapper reuse, replacement with a distinct shared provider, and freezing the shared snapshot when switching to a provider that does not share with subagents.

GitOrigin-RevId: 7b6b2f9031f206e203f6fa14e1478a36b305ec39
This commit is contained in:
Bryan Ashley
2026-10-01 20:44:54 +00:00
committed by copyberry
parent 960e878df4
commit 9d53dd91b5
2 changed files with 68 additions and 14 deletions
@@ -1,4 +1,5 @@
//! Keeps one replaceable instruction provider per root while its agent tree is alive.
//! Reusing the shared wrapper must never make it delegate to itself.
use codex_extension_api::Instructions;
use codex_extension_api::LoadInstructionsFuture;
@@ -38,10 +39,15 @@ impl SharedThreadInstructionsProviders {
}
match (existing, provider) {
(Some(existing), provider) => {
if provider.is_some() {
// An unloaded root can inherit this wrapper from its surviving children.
let shared: Arc<dyn ThreadInstructionsProvider> = existing.clone();
if provider
.as_ref()
.is_some_and(|provider| !Arc::ptr_eq(provider, &shared))
{
existing.replace(provider);
}
Some(existing)
Some(shared)
}
(None, Some(provider)) => {
let shared = Arc::new(SharedThreadInstructionsProvider(Mutex::new(