iOfficeAI/OfficeCLI · error · CliException
plugin_stream_closed
plugin_stream_closed
Error message
Format-handler session for '{_plugin.Manifest.Name}' is no longer usable (stream was closed earlier). What it means
CliException (code 'plugin_stream_closed') thrown by Send when the _broken flag is set. _broken is latched at six sites (FormatHandlerSession.cs:212,234,243,261,268,297) after an earlier IO/protocol failure on the session's stdin/stdout. Once broken, the session refuses further sends rather than retry into a half-dead stream.
Source
Thrown at src/officecli/Core/Plugins/FormatHandlerSession.cs:155
? null
: JsonSerializer.Deserialize(reply.ToJsonString(), PluginJsonContext.Default.PluginSessionCapabilities);
}
catch (JsonException)
{
_sessionCaps = null;
}
}
/// <summary>
/// Send a request envelope and synchronously wait for the matching reply.
/// Throws <see cref="CliException"/> on protocol error, IO failure, or
/// plugin-reported error responses.
/// </summary>
public JsonNode? Send(string msgType, string? command, JsonObject? args = null, JsonObject? props = null)
{
if (_disposed) throw new ObjectDisposedException(nameof(FormatHandlerSession));
if (_broken)
throw new CliException(
$"Format-handler session for '{_plugin.Manifest.Name}' is no longer usable (stream was closed earlier).")
{ Code = "plugin_stream_closed" };
// Capability gate: short-circuit verbs the plugin already declared it
// does not support, avoiding a wasted round-trip and ambiguous errors.
if (command is not null && _sessionCaps?.Capabilities?.Commands is { Count: > 0 } cmds
&& !cmds.Contains(command))
{
throw new CliException(
$"Format-handler plugin '{_plugin.Manifest.Name}' does not implement command '{command}'.")
{ Code = "unsupported_command" };
}
var verbForTimeout = command ?? msgType;
var idle = _plugin.Manifest.ResolveIdleTimeout(verbForTimeout);
return SendRaw(msgType, command, args, props, idle);
}
View on GitHub (pinned to 1ced45e900)
Solutions
- Recreate the format-handler session (restart the plugin) instead of reusing the broken one.
- Investigate the original failure that set _broken — the first error in the log is the root cause, not this one.
- For resident hosts, add session-health checks and recycle the proxy on break.
Defensive patterns
Strategy: fallback
Validate before calling
if (session.IsBroken) { session = new FormatHandlerSession(plugin); /* recycle */ } Type guard
static bool IsSessionUsable(FormatHandlerSession s) => !s.IsBroken && !s.IsDisposed;
Try / catch
try { session.Send(...); }
catch (CliException ex) when (ex.Code == "plugin_stream_closed")
{ session = new FormatHandlerSession(plugin); /* recreate and retry once */ } Prevention
- Investigate the first error in the log — _broken is a downstream symptom.
- Recycle the proxy/session after any IO error rather than reusing it.
- Add liveness checks for resident sessions whose plugins may restart.
When it happens
Trigger: Any Send call after a prior request hit an IO error, malformed reply, or stream closure that set _broken=true. ObjectDisposedException is checked first (for explicit disposal).
Common situations: Plugin crashed and the session tried to keep going; a previous command timed out / broke the wire format and subsequent commands reuse the same proxy; long-lived resident session whose plugin was restarted underneath.
Related errors
AI-assisted analysis of iOfficeAI/OfficeCLI@1ced45e900 (2026-08-13).
Data as JSON: /api/errors/9c830b1ecdc9ce61.
Report an issue: GitHub.