iOfficeAI/OfficeCLI · error · CliException
plugin_idle_timeout
plugin_idle_timeout
Error message
Format-handler plugin '{_plugin.Manifest.Name}' produced no activity for {idleTimeoutSec}s (command={verbForError}). What it means
Thrown when the plugin produces no activity (no stdout reply and no stderr heartbeat) for idleTimeoutSec seconds during a Send call. The idle watchdog polls stdout at intervals and checks _lastActivityTicks, which the stderr pump resets whenever it sees a {"heartbeat":true} line. This fires when the plugin is stuck, deadlocked, or doing very long work without heartbeating. The plugin process is killed (TryKill) and the session is marked _broken.
Source
Thrown at src/officecli/Core/Plugins/FormatHandlerSession.cs:299
/// slow to reply.
/// </summary>
private string? ReadReplyWithIdleWatchdog(int idleTimeoutSec, string verbForError)
{
var budgetTicks = TimeSpan.FromSeconds(idleTimeoutSec).Ticks;
var readTask = Task.Run(() => _stdoutReader!.ReadLine());
while (!readTask.IsCompleted)
{
// Poll at one-quarter the budget (250ms floor) so even short
// timeouts fire reasonably close to the configured deadline.
var pollMs = Math.Max(250, idleTimeoutSec * 1000 / 4);
if (readTask.Wait(pollMs)) break;
var since = DateTime.UtcNow.Ticks - Volatile.Read(ref _lastActivityTicks);
if (since > budgetTicks)
{
_broken = true;
TryKill();
throw new CliException(
$"Format-handler plugin '{_plugin.Manifest.Name}' produced no activity for {idleTimeoutSec}s (command={verbForError}).")
{
Code = "plugin_idle_timeout",
Suggestion = $"Raise `idle_timeout_seconds.verbs.{verbForError}` in the plugin's manifest, " +
"emit periodic `{\"heartbeat\":true}` on stderr during long jobs, or pass --timeout 0 to disable.",
};
}
}
// readTask completed — propagate exceptions, then take the result.
var line = readTask.GetAwaiter().GetResult();
if (line is not null)
Volatile.Write(ref _lastActivityTicks, DateTime.UtcNow.Ticks);
return line;
}
public void Dispose()
{View on GitHub (pinned to 1ced45e900)
Solutions
- Raise the timeout in the plugin manifest: set idle_timeout_seconds.verbs.<verb> or idle_timeout_seconds.default to a higher value.
- Add heartbeat emission to the plugin: periodically write {"heartbeat":true} to stderr during long operations (the stderr pump resets the activity timer).
- Disable the watchdog entirely: pass --timeout 0, or set environment variable OFFICECLI_PLUGIN_IDLE_TIMEOUT_SECONDS=0.
- Profile the plugin to find the bottleneck — if it genuinely needs minutes, heartbeating is the correct fix, not just raising the limit.
- Check for deadlocks: file locks, network hangs, interactive prompts blocking on stdin.
Example fix
// before: plugin does long work with no heartbeat
def handle_save(msg):
do_heavy_conversion(msg['args']) # takes 120s, default timeout is 60s
return ok(result)
// after: emit heartbeats on stderr during long work
def handle_save(msg):
for chunk in do_heavy_conversion_chunked(msg['args']):
sys.stderr.write('{"heartbeat":true}\n')
sys.stderr.flush()
return ok(result)
// manifest fix: raise the verb-specific budget
"idle_timeout_seconds": { "default": 60, "verbs": { "save": 300 } } Defensive patterns
Strategy: validation
Validate before calling
// Pre-check the timeout budget for a verb before sending
var budget = plugin.Manifest.ResolveIdleTimeout(command ?? msgType);
if (budget == 0)
{
// Watchdog disabled — Send will block until the plugin replies or dies.
}
else if (estimatedWorkSeconds > budget)
{
// Raise the budget or disable: set OFFICECLI_PLUGIN_IDLE_TIMEOUT_SECONDS env var,
// or update the manifest's idle_timeout_seconds.verbs.<verb>.
Console.Error.WriteLine($"Warning: estimated {estimatedWorkSeconds}s work exceeds {budget}s budget.");
} Try / catch
try
{
var result = session.Send(msgType, command, args, props);
}
catch (CliException ex) when (ex.Code == "plugin_idle_timeout")
{
// Plugin hung without heartbeating. The process was killed (TryKill).
// Read ex.Suggestion for remediation guidance.
session.Dispose();
// To retry: raise the timeout or add heartbeats, then spawn a new session.
throw;
} Prevention
- For long operations, emit {"heartbeat":true} on stderr periodically from the plugin.
- Set idle_timeout_seconds.verbs.<verb> in the manifest for known-slow verbs.
- Use OFFICECLI_PLUGIN_IDLE_TIMEOUT_SECONDS=0 to disable the watchdog for debugging.
- Profile plugin performance to set realistic timeout budgets.
- Check for deadlocks: file locks, network hangs, interactive stdin prompts.
When it happens
Trigger: FormatHandlerSession.SendRaw calls ReadReplyWithIdleWatchdog(idleTimeoutSec, verb). The read task doesn't complete within the budget, and no stderr heartbeat has reset the timer. The budget comes from PluginManifest.ResolveIdleTimeout(verb) — either the manifest's idle_timeout_seconds or the 60s SafeDefault.
Common situations: Plugin does CPU-heavy work (large file rendering, conversion) that takes longer than the 60s default and doesn't emit stderr heartbeats. Plugin deadlocks waiting on a file lock or network resource. Plugin's heartbeat logic has a bug (wrong stream, wrong JSON shape). Plugin waits for user input interactively. Large PowerPoint with hundreds of slides.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- plugin_create_failed
- Pipe communication timed out
- -1
- Unsupported file type: {ext}. Supported: .docx, .xlsx, .pptx
- plugin_idle_timeout
AI-assisted analysis of iOfficeAI/OfficeCLI@1ced45e900 (2026-08-13).
Data as JSON: /api/errors/a4316e6097bc22a0.
Report an issue: GitHub.