ObsidianService.#request wrapped every call in withRetry. The framework's
default isTransient predicate retries on 502/503/504/429 and raw network
errors (UND_ERR_SOCKET, ECONNRESET, ETIMEDOUT) — fine for idempotent reads,
but non-idempotent POST/PATCH would double-apply when the upstream succeeded
and the response was lost. Affects obsidian_append_to_note, obsidian_patch_note
(append/prepend), obsidian_execute_command, obsidian_open_in_ui, and POST-mode
obsidian_search_notes.
- Added RETRY_SAFE_METHODS allowlist { GET, PUT, DELETE }; everything else
bypasses the retry branch entirely. Trade-off: POSTs surface transient
5xx/network blips immediately rather than auto-retrying — data integrity
wins over single-call resilience for non-idempotent paths.
- 13 regression tests under "ObsidianService retry policy" — POST/PATCH no
retry on 503/504/raw network errors, GET/PUT/DELETE retry on 503 and
ECONNRESET then succeed, non-transient 4xx/5xx never retry.
- tests/helpers.ts exports PathMatcher / DispatchOpts / DynamicReply / ReplyFn.
- vitest ^4.1.5 → ^4.1.6 (devDependency).
See changelog/3.1.x/3.1.8.md for the full release notes.