{"record":{"id":"f18b08e50bed1584","repo":"iOfficeAI/OfficeCLI","slug":"1-f18b08","errorCode":"-1","errorMessage":"resident is running but the command could not be delivered (pipe busy or unresponsive); retry, or close and reopen [{e}]","messagePattern":"resident is running but the command could not be delivered \\(pipe busy or unresponsive\\); retry, or close and reopen \\[(.+?)\\]","errorType":"exception","errorClass":"OfficeCliError","httpStatus":null,"severity":"error","filePath":"sdk/python/officecli.py","lineNumber":224,"sourceCode":"    \"\"\"Forward one request, mirroring officecli's TrySend: bounded connect + a few\n    retries with backoff, then a blocking read. A retry only re-attempts the\n    connect (before the command runs), so it never double-applies a mutation. If\n    the command still can't be delivered, raise a busy/unresponsive error — never\n    fall back to touching the file directly (that would race the resident).\n\n    `max_retries` overrides the busy-retry count. Liveness probes (_serves) pass 0\n    so a missing/stale pipe fails FAST instead of sleeping through ~0.3s of backoff\n    — retrying a probe the resident isn't answering can't make it answer; the\n    busy-retry policy is for delivering a real command to a slow-but-live pipe.\"\"\"\n    line = (json.dumps(req, ensure_ascii=False) + \"\\n\").encode(\"utf-8\")\n    send = _send_win if _IS_WIN else _send_unix\n    for attempt in range(max_retries + 1):\n        try:\n            raw = send(sock_path, line, connect_timeout)\n            break\n        except OSError as e:\n            if attempt >= max_retries:\n                raise OfficeCliError(-1,\n                    f\"resident is running but the command could not be delivered \"\n                    f\"(pipe busy or unresponsive); retry, or close and reopen [{e}]\")\n            time.sleep(0.05 * (attempt + 1))    # = TrySend's 50*(n+1)ms backoff\n    # utf-8-sig: the resident's StreamWriter (Encoding.UTF8) prepends a BOM the\n    # C# StreamReader strips; we must too, or json.loads chokes on the leading .\n    text = raw.decode(\"utf-8-sig\")\n    if not text.strip():\n        # Empty/closed reply: the resident accepted the connection but closed\n        # without a complete response (e.g. crashed mid-serve). We refuse to\n        # re-send — the command may already have been APPLIED before the resident\n        # died, so re-sending would double-apply a non-idempotent op — and raise\n        # instead. officecli's TrySend now matches: its retry covers only the\n        # connect phase (before the command is written); on an empty reply after a\n        # successful write it returns null without re-sending, the C# equivalent of\n        # this raise. _cmd's recovery then restarts a dead resident and retries once\n        # (a fresh connect, before re-send), and _serves()/alive() (which swallow\n        # OfficeCliError) read an empty reply as \"not alive\".\n        raise OfficeCliError(-1,","sourceCodeStart":206,"sourceCodeEnd":242,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/sdk/python/officecli.py#L206-L242","documentation":"Raised by the Python SDK (code -1) when sending a command to the resident pipe fails with OSError on every attempt up to max_retries+1. The resident is running (the connect succeeded) but the pipe is busy or unresponsive during delivery. The SDK retries with 50*(n+1)ms backoff before giving up; liveness probes (_serves) pass max_retries=0 so a stale pipe fails fast instead of sleeping.","triggerScenarios":"High pipe contention from many concurrent SDK clients; a slow/hung resident that accepts connections but cannot drain commands; transient OS pipe errors under load. Surfaces after all configured retries are exhausted.","commonSituations":"Many threads/processes sharing one resident; a large batch blocking the resident so single commands time out on delivery; antivirus or sandbox interfering with the named pipe/socket.","solutions":["Retry the operation at the caller level with backoff — the failure is often transient.","Reduce concurrency against the single resident, or batch writes to lower round-trips.","Close and reopen the Document (restarts the resident) if it stays unresponsive."],"exampleFix":"# before\ndoc.set('/Sheet1/A1', {'text': 'x'})\n# after — caller retry with backoff\nimport time\nfor attempt in range(5):\n    try:\n        doc.set('/Sheet1/A1', {'text': 'x'}); break\n    except officecli.OfficeCliError as e:\n        if attempt == 4: raise\n        time.sleep(0.1 * (attempt + 1))","handlingStrategy":"retry","validationCode":"null","typeGuard":"null","tryCatchPattern":"import time\nimport officecli\n\ndef send_resilient(doc, fn, *args, retries=4, **kw):\n    last = None\n    for attempt in range(retries + 1):\n        try:\n            return fn(*args, **kw)\n        except officecli.OfficeCliError as e:\n            last = e\n            if 'could not be delivered' not in str(e) or attempt == retries:\n                raise\n            time.sleep(0.1 * (attempt + 1))\n    raise last","preventionTips":["Wrap deliveries in a caller-side retry with backoff for transient pipe contention.","Lower concurrency against a single resident, or batch writes.","Reopen the Document to restart an unresponsive resident."],"tags":["python-sdk","transport","pipe","retry","concurrency"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}