Describe the bug
In Shannon v2.5.2, a Pi task sub-session receives the parent agent's ResourceLoader. When the child finishes, subSession.dispose() invalidates the extension runtime held by that shared loader. The parent session—and parallel sibling task sessions using the same loader—then receive Pi's deterministic stale-context error from subsequent tools:
This extension ctx is stale after session replacement or reload.
The agent can continue producing LLM turns after its tools are unusable, so the visible failure is often a later and misleading OutputValidationError. In one run, pre-recon emitted a blocked message after every tool became stale; downstream vulnerability agents later repeatedly failed output validation after doing substantial work.
This appears provider-independent: it is caused by local session/resource ownership, after a child task completes successfully.
Steps to reproduce
-
Check out the latest release, v2.5.2, and build/run in local mode.
-
Configure a source-available authorized target large enough for an agent to delegate work through the task tool. Parallel task calls make the failure especially easy to reproduce.
-
Start a fresh workspace:
./shannon start \
--url http://host.docker.internal:<port> \
--repo /path/to/private-repo \
--config /path/to/redacted-config.yaml \
--workspace redacted-fresh-workspace \
--follow
-
Inspect workspaces/<workspace>/.shannon/agents/*pre-recon* or a vulnerability-agent log.
-
Observe that child task calls complete, then later parent tools such as read, ls, todo_write, or deliverable collector tools return the stale-extension-context message.
-
The agent eventually finishes without required deliverables, leading to output-validation retries/failure.
Expected behaviour
Each child task should own an isolated extension runtime. Disposing one child session must not invalidate its parent or parallel siblings. If a stale-runtime condition somehow occurs anyway, Shannon should stop that agent immediately with a specific execution error rather than continue spending tokens and surface a generic output-validation failure.
Actual behaviour
The parent and all task children share one loader/runtime. The first child disposal invalidates that runtime. Subsequent tools fail deterministically, but the agent continues generating turns and may retry required output calls until Temporal reports an OutputValidationError.
Pre-submission checklist (required)
If applicable
Debugging details
Sanitized agent-log sequence after delegated tasks completed:
tool_end read -> This extension ctx is stale after session replacement or reload. ...
tool_end todo_write -> This extension ctx is stale after session replacement or reload. ...
tool_end ls -> This extension ctx is stale after session replacement or reload. ...
tool_end collector -> This extension ctx is stale after session replacement or reload. ...
LLM -> Blocked by a Pi harness failure: all tools now return `ctx is stale ...`
The same message repeated for every parent tool call. A downstream Temporal failure surfaced only the secondary symptom:
ApplicationFailure: Agent injection-vuln failed output validation
applicationFailureInfo.type: OutputValidationError
Root cause in current source
Current main still has the same ownership chain as v2.5.2:
pi-executor.ts creates one parent loader and passes it to createTaskTool.
task-tool.ts passes that exact loader into every child createAgentSession.
task is explicitly parallel, so sibling sessions can also share the same loader concurrently.
subSession.dispose() always runs.
In @earendil-works/pi-coding-agent 0.82.1, DefaultResourceLoader caches the loaded extension runtime. Each AgentSession builds its ExtensionRunner from that runtime, and AgentSession.dispose() invalidates the runner/runtime. Therefore a shared loader is not safe across independently disposed sessions.
Tested fix
A minimal local patch on the v2.5.2 baseline fixed the ownership problem:
interface TaskToolContext {
- resourceLoader: ResourceLoader;
+ createResourceLoader: () => Promise<ResourceLoader>;
}
async execute(...) {
+ const resourceLoader = await config.createResourceLoader();
const { session: subSession } = await createAgentSession({
...,
resourceLoader,
});
}
The parent executor supplies a factory using the same existing buildResourceLoader(cwd, logger, agentName) path. This preserves Shannon's extension, skill, browser-agent, permission-system, and bash-timeout configuration while giving every task invocation its own loader/runtime. The factory must be called inside each execute, not once when constructing the parallel tool.
I also added a defensive check on tool_execution_end for the exact stale-context prefix. If observed, it records an AGENT_EXECUTION_FAILED, aborts the session, and preserves that specific non-retryable error rather than allowing the run to degrade into output-validation retries. Parent-session disposal was moved into finally so failure paths are cleaned up too.
Local regression coverage:
- Two concurrent
task.execute calls receive distinct loader objects and both child sessions dispose independently.
- The stale-context detector matches the Pi text result but not ordinary text/image tool results.
- TypeScript
--noEmit check and build pass.
- Both regression tests pass on the host and inside the rebuilt
shannon-worker image.
- A fresh patched workflow entered pre-recon attempt 1 without an immediate failure at the time of filing. Full end-to-end execution is still in progress, so this last point is preliminary rather than a completion claim.
Related: #62 describes the broad OutputValidationError symptom in older runs, with several unrelated causes. This report is separate because it has a deterministic v2.5.2 Pi lifecycle signature and a source-level ownership cause.
Screenshots
Not included; the exact sanitized agent-log and Temporal failure text are provided above.
CLI mode
Local (./shannon)
Provider
OpenAI (Codex OAuth through Pi authentication)
Full command with all flags used (with redactions)
./shannon start --url http://host.docker.internal:<port> --repo /path/to/private-repo --config /path/to/redacted-config.yaml --workspace redacted-fresh-workspace --follow
OS (with version)
macOS 26.6 (Build 25G72), Apple Silicon
Node.js version (node -v)
v22.22.2
Docker version (docker -v)
Docker version 29.4.0, build 9d7ad9f
Additional context
- Shannon release:
v2.5.2
- Shannon baseline commit:
12d1c48a78658af9b59cd36f12d8b683a684b1d3
- Pi coding-agent dependency:
0.82.1
- Current
main inspected at 53118c62032747b039e3abbe895096c5eb9ec8e1 still contains the shared-loader path.
- No PR is being opened because the project README says external code contributions are not currently accepted.
Full Temporal stack trace
The initial report included the failure summary but omitted the actual stack trace. This is the sanitized trace from the failed vulnerability-analysis activity:
ApplicationFailure: Agent injection-vuln failed output validation
at ApplicationFailure.create (/app/node_modules/.pnpm/@temporalio+common@1.15.0/node_modules/@temporalio/common/lib/failure.js:183:16)
at runAgentActivity (file:///app/apps/worker/dist/temporal/activities.js:164:48)
at async Activity.execute (/app/node_modules/.pnpm/@temporalio+worker@1.15.0_tslib@2.8.1/node_modules/@temporalio/worker/lib/activity.js:101:20)
at async NativeConnection.withAbortSignal (/app/node_modules/.pnpm/@temporalio+worker@1.15.0_tslib@2.8.1/node_modules/@temporalio/worker/lib/connection.js:172:16)
at async Client.withAbortSignal (/app/node_modules/.pnpm/@temporalio+client@1.15.0/node_modules/@temporalio/client/lib/base-client.js:65:16)
at async /app/node_modules/.pnpm/@temporalio+worker@1.15.0_tslib@2.8.1/node_modules/@temporalio/worker/lib/activity.js:161:32
at async /app/node_modules/.pnpm/@temporalio+worker@1.15.0_tslib@2.8.1/node_modules/@temporalio/worker/lib/worker.js:77
Associated failure details:
{
"message": "Agent injection-vuln failed output validation",
"source": "TypeScriptSDK",
"applicationFailureInfo": {
"type": "OutputValidationError",
"details": {
"payloads": [
{
"agentName": "injection-vuln",
"attemptNumber": 1,
"elapsed": 170931
}
]
}
}
}
Describe the bug
In Shannon v2.5.2, a Pi
tasksub-session receives the parent agent'sResourceLoader. When the child finishes,subSession.dispose()invalidates the extension runtime held by that shared loader. The parent session—and parallel sibling task sessions using the same loader—then receive Pi's deterministic stale-context error from subsequent tools:The agent can continue producing LLM turns after its tools are unusable, so the visible failure is often a later and misleading
OutputValidationError. In one run, pre-recon emitted a blocked message after every tool became stale; downstream vulnerability agents later repeatedly failed output validation after doing substantial work.This appears provider-independent: it is caused by local session/resource ownership, after a child task completes successfully.
Steps to reproduce
Check out the latest release,
v2.5.2, and build/run in local mode.Configure a source-available authorized target large enough for an agent to delegate work through the
tasktool. Parallel task calls make the failure especially easy to reproduce.Start a fresh workspace:
Inspect
workspaces/<workspace>/.shannon/agents/*pre-recon*or a vulnerability-agent log.Observe that child
taskcalls complete, then later parent tools such asread,ls,todo_write, or deliverable collector tools return the stale-extension-context message.The agent eventually finishes without required deliverables, leading to output-validation retries/failure.
Expected behaviour
Each child task should own an isolated extension runtime. Disposing one child session must not invalidate its parent or parallel siblings. If a stale-runtime condition somehow occurs anyway, Shannon should stop that agent immediately with a specific execution error rather than continue spending tokens and surface a generic output-validation failure.
Actual behaviour
The parent and all task children share one loader/runtime. The first child disposal invalidates that runtime. Subsequent tools fail deterministically, but the agent continues generating turns and may retry required output calls until Temporal reports an
OutputValidationError.Pre-submission checklist (required)
shannon(v2.5.2).If applicable
Debugging details
Sanitized agent-log sequence after delegated tasks completed:
The same message repeated for every parent tool call. A downstream Temporal failure surfaced only the secondary symptom:
Root cause in current source
Current
mainstill has the same ownership chain as v2.5.2:pi-executor.tscreates one parent loader and passes it tocreateTaskTool.task-tool.tspasses that exact loader into every childcreateAgentSession.taskis explicitly parallel, so sibling sessions can also share the same loader concurrently.subSession.dispose()always runs.In
@earendil-works/pi-coding-agent0.82.1,DefaultResourceLoadercaches the loaded extension runtime. EachAgentSessionbuilds itsExtensionRunnerfrom that runtime, andAgentSession.dispose()invalidates the runner/runtime. Therefore a shared loader is not safe across independently disposed sessions.Tested fix
A minimal local patch on the v2.5.2 baseline fixed the ownership problem:
interface TaskToolContext { - resourceLoader: ResourceLoader; + createResourceLoader: () => Promise<ResourceLoader>; } async execute(...) { + const resourceLoader = await config.createResourceLoader(); const { session: subSession } = await createAgentSession({ ..., resourceLoader, }); }The parent executor supplies a factory using the same existing
buildResourceLoader(cwd, logger, agentName)path. This preserves Shannon's extension, skill, browser-agent, permission-system, and bash-timeout configuration while giving every task invocation its own loader/runtime. The factory must be called inside eachexecute, not once when constructing the parallel tool.I also added a defensive check on
tool_execution_endfor the exact stale-context prefix. If observed, it records anAGENT_EXECUTION_FAILED, aborts the session, and preserves that specific non-retryable error rather than allowing the run to degrade into output-validation retries. Parent-session disposal was moved intofinallyso failure paths are cleaned up too.Local regression coverage:
task.executecalls receive distinct loader objects and both child sessions dispose independently.--noEmitcheck and build pass.shannon-workerimage.Related: #62 describes the broad
OutputValidationErrorsymptom in older runs, with several unrelated causes. This report is separate because it has a deterministic v2.5.2 Pi lifecycle signature and a source-level ownership cause.Screenshots
Not included; the exact sanitized agent-log and Temporal failure text are provided above.
CLI mode
Local (
./shannon)Provider
OpenAI (Codex OAuth through Pi authentication)
Full command with all flags used (with redactions)
OS (with version)
macOS 26.6 (Build 25G72), Apple Silicon
Node.js version (
node -v)v22.22.2Docker version (
docker -v)Docker version 29.4.0, build 9d7ad9fAdditional context
v2.5.212d1c48a78658af9b59cd36f12d8b683a684b1d30.82.1maininspected at53118c62032747b039e3abbe895096c5eb9ec8e1still contains the shared-loader path.Full Temporal stack trace
The initial report included the failure summary but omitted the actual stack trace. This is the sanitized trace from the failed vulnerability-analysis activity:
Associated failure details:
{ "message": "Agent injection-vuln failed output validation", "source": "TypeScriptSDK", "applicationFailureInfo": { "type": "OutputValidationError", "details": { "payloads": [ { "agentName": "injection-vuln", "attemptNumber": 1, "elapsed": 170931 } ] } } }