{"record":{"id":"a4316e6097bc22a0","repo":"iOfficeAI/OfficeCLI","slug":"plugin-idle-timeout-a4316e","errorCode":"plugin_idle_timeout","errorMessage":"Format-handler plugin '{_plugin.Manifest.Name}' produced no activity for {idleTimeoutSec}s (command={verbForError}).","messagePattern":"Format-handler plugin '(.+?)' produced no activity for (.+?)s \\(command=(.+?)\\)\\.","errorType":"exception","errorClass":"CliException","httpStatus":null,"severity":"error","filePath":"src/officecli/Core/Plugins/FormatHandlerSession.cs","lineNumber":299,"sourceCode":"    /// slow to reply.\n    /// </summary>\n    private string? ReadReplyWithIdleWatchdog(int idleTimeoutSec, string verbForError)\n    {\n        var budgetTicks = TimeSpan.FromSeconds(idleTimeoutSec).Ticks;\n        var readTask = Task.Run(() => _stdoutReader!.ReadLine());\n\n        while (!readTask.IsCompleted)\n        {\n            // Poll at one-quarter the budget (250ms floor) so even short\n            // timeouts fire reasonably close to the configured deadline.\n            var pollMs = Math.Max(250, idleTimeoutSec * 1000 / 4);\n            if (readTask.Wait(pollMs)) break;\n            var since = DateTime.UtcNow.Ticks - Volatile.Read(ref _lastActivityTicks);\n            if (since > budgetTicks)\n            {\n                _broken = true;\n                TryKill();\n                throw new CliException(\n                    $\"Format-handler plugin '{_plugin.Manifest.Name}' produced no activity for {idleTimeoutSec}s (command={verbForError}).\")\n                {\n                    Code = \"plugin_idle_timeout\",\n                    Suggestion = $\"Raise `idle_timeout_seconds.verbs.{verbForError}` in the plugin's manifest, \" +\n                                 \"emit periodic `{\\\"heartbeat\\\":true}` on stderr during long jobs, or pass --timeout 0 to disable.\",\n                };\n            }\n        }\n\n        // readTask completed — propagate exceptions, then take the result.\n        var line = readTask.GetAwaiter().GetResult();\n        if (line is not null)\n            Volatile.Write(ref _lastActivityTicks, DateTime.UtcNow.Ticks);\n        return line;\n    }\n\n    public void Dispose()\n    {","sourceCodeStart":281,"sourceCodeEnd":317,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/Core/Plugins/FormatHandlerSession.cs#L281-L317","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: plugin does long work with no heartbeat\ndef handle_save(msg):\n    do_heavy_conversion(msg['args'])  # takes 120s, default timeout is 60s\n    return ok(result)\n\n// after: emit heartbeats on stderr during long work\ndef handle_save(msg):\n    for chunk in do_heavy_conversion_chunked(msg['args']):\n        sys.stderr.write('{\"heartbeat\":true}\\n')\n        sys.stderr.flush()\n    return ok(result)\n\n// manifest fix: raise the verb-specific budget\n\"idle_timeout_seconds\": { \"default\": 60, \"verbs\": { \"save\": 300 } }","handlingStrategy":"validation","validationCode":"// Pre-check the timeout budget for a verb before sending\nvar budget = plugin.Manifest.ResolveIdleTimeout(command ?? msgType);\nif (budget == 0)\n{\n    // Watchdog disabled — Send will block until the plugin replies or dies.\n}\nelse if (estimatedWorkSeconds > budget)\n{\n    // Raise the budget or disable: set OFFICECLI_PLUGIN_IDLE_TIMEOUT_SECONDS env var,\n    // or update the manifest's idle_timeout_seconds.verbs.<verb>.\n    Console.Error.WriteLine($\"Warning: estimated {estimatedWorkSeconds}s work exceeds {budget}s budget.\");\n}","typeGuard":null,"tryCatchPattern":"try\n{\n    var result = session.Send(msgType, command, args, props);\n}\ncatch (CliException ex) when (ex.Code == \"plugin_idle_timeout\")\n{\n    // Plugin hung without heartbeating. The process was killed (TryKill).\n    // Read ex.Suggestion for remediation guidance.\n    session.Dispose();\n    // To retry: raise the timeout or add heartbeats, then spawn a new session.\n    throw;\n}","preventionTips":["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."],"tags":["plugin","timeout","watchdog","heartbeat","ipc"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}