Skip to content

[BUG] docx: resident's unflushed in-memory edits silently lost if resident dies before idle auto-flush; next process reports "No pending changes" #328

Description

@huanghai19860123-hash

Summary

When a live resident holds a .docx and a mutation (e.g. a tracked-change edit via --prop revision.type=ins) is applied through it, the change exists only in the resident's memory until the idle auto-flush (adaptive 2–10s) fires. If the resident process dies inside that window (SIGKILL, host-harness child reaping, crash), the dirty state is silently lost: the next officecli command spawns a fresh resident from the stale on-disk file, query/get show the pre-edit content, and save/close report "No pending changes" — actively telling the caller everything was saved when the edit is gone.

Field context: hit in an AI-agent harness (MyAgents) that runs each tool call in a separate short-lived exec segment. An agent did a tracked-change replace (matched: 1, same-process get showed revision.type=ins with the expected author), then the next exec segment saw ins/del counts back at 0, file content reverted, and save/close claiming already-saved. In that environment the resident evidently did not survive between segments.

Environment

  • officecli 1.0.143, macOS arm64 (Darwin 25.5.0)
  • default resident behavior (auto-kept-open after create / explicit open)

Minimal reproduction (confirmed)

officecli create test.docx           # resident kept open
officecli add test.docx /body --type paragraph --prop text="hello world"
officecli close test.docx            # clean baseline on disk

officecli open test.docx             # live resident
officecli set test.docx "/body/p[1]/run[1]" \
  --prop text="hello brave world" --prop revision.type=ins --prop revision.author=X
unzip -p test.docx word/document.xml | grep -c 'w:ins'   # -> 0  (deferred, as designed)

kill -9 "$(pgrep -f '__resident-serve__' | head -1)"      # simulate harness reaping / crash
sleep 1

unzip -p test.docx word/document.xml | grep -c 'w:ins'                    # -> 0   (edit lost)
officecli query test.docx "revision[@type=ins]" --json                    # -> matches: 0
officecli get   test.docx "/body/p[1]" --json                             # -> "hello world" (reverted)
officecli save  test.docx --json                                          # -> "No pending changes"  (misleading)

Control observations, same version:

  • If set itself starts the resident (no pre-existing one), the command flushes before returning — no loss window.
  • OFFICECLI_RESIDENT_FLUSH=each works as documented: disk shows w:ins immediately after the mutating command returns. This is the workaround we now use in agent contexts.

Expected

Either (a) an acknowledged mutation survives resident death — e.g. journal/WAL replayed on next open, or flush-before-ack; or at minimum (b) the next process can detect that a resident died with unflushed changes and say so, instead of save reporting "No pending changes" as if the edit had been persisted.

Suggestions

  1. Crash-safe durability for resident dirty state (WAL or flush-before-ack for mutating commands; keep deferred flush for reads).
  2. Honest recovery messaging: distinguish "nothing was ever pending" from "a previous resident terminated abnormally".
  3. Document a supported way to disable auto-resident entirely for harnesses with short-lived process trees (OFFICECLI_NO_AUTO_RESIDENT does not exist in 1.0.143 — only OFFICECLI_RESIDENT_FLUSH is documented), or document OFFICECLI_RESIDENT_FLUSH=each as the recommended agent-mode setting.

Related in spirit but distinct: #244 (xlsx batch atomicity under IPC failure).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions