Bug 1 — search::start folder_path format (already fixed in 314fae9):
json.dumps([path]) is confirmed correct per official Synology API docs
and multiple independent implementations (N4S4/synology-api, kwent/syno).
Poll-loop last-non-empty guard (if current_files:) is also in place.
No further change needed for Bug 1.
Bug 2 — extract::start wrong parameter key:
The previous fix attempt renamed "file_path" → "path", which was wrong.
Official API docs and independent implementations confirm the key is
"file_path". The json.dumps() wrapping on file_path and dest_folder_path
was already correct. Reverted the key rename.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bug 1 — search always returned empty results:
Search::start was passing folder_path as a plain string.
DSM silently ignores a plain string for this parameter and returns
finished=True with files=[] immediately, as if nothing was found.
Fix: json.dumps([path]) — JSON array, matching the multi-path API
pattern used by DirSize::start and List::getinfo.
Bug 2 — extract returned DSM error 408:
Extract::start was using "file_path" as the parameter key for the
source archive. DSM expects "path". Without a valid path DSM returned
error 408. The json.dumps wrapping was already correct.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DSM's DirSize and MD5 background service needs ~6-8 s to initialise
after a period of inactivity. During this cold-start window tasks are
registered but every status poll returns error 599 ("no such task").
Replace the ad-hoc start+_poll_task call in dir_size and get_md5 with
a new _start_and_poll_oneshot helper that:
- polls with exponential backoff (0.2 s → cap 2 s)
- on 5 consecutive 599s: restarts the task (up to 6 attempts total)
- honours a shared 60 s wall-clock budget across all restarts
- returns a clear error if all restart attempts are exhausted
Root cause confirmed by test_dirsize_md5.py: after ~6 s / 2 restarts
the service warms up and the very first poll on the new task succeeds.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All changes since 0.2.2 to _poll_task, dir_size, and get_md5 (window_timeout,
_poll_oneshot, start_and_poll_immediately) are reverted. The 0.2.2 behaviour
worked reliably for small directories and is the last known-good baseline.
The remaining known limitation (occasional 599 on large directories) is
documented in SPEC.md. Retry the operation as a workaround.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add FileStationClient.start_and_poll_immediately: starts the async task and
immediately makes the first status poll within the same method, with no
intermediate awaits other than the two HTTP calls. This minimises scheduler
latency between start and first poll for one-shot tasks.
_poll_oneshot now accepts the first_status from start_and_poll_immediately:
- finished=True on first poll → return immediately
- finished=False → Phase 2 (exponential backoff, 60 s timeout)
- None (first poll was 599) → burst-retry 10× at 10 ms, then Phase 2
(Phase 2 keeps polling through 599 until seen_alive, then fails fast)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace _poll_task for one-shot tasks with _poll_oneshot, which uses two
phases: (1) a burst of up to 11 immediate polls at 50ms intervals to catch
tasks that complete in <500ms, and (2) exponential-backoff polling once
finished=False is observed. A 599 during burst → window missed (fail fast).
A 599 during Phase 2 (task was seen running) → same. _poll_task is
simplified back to a plain long-poll with no window_timeout logic.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_poll_task now accepts window_timeout. For DirSize and MD5 (one-shot result
windows), if only 599 errors arrive for window_timeout seconds without ever
seeing the task alive (finished=False), return a fast "result window missed —
please retry" error instead of waiting the full 60 s. Tasks that return
finished=False at least once (large dirs, large files) are unaffected.
Also removes the stale [dsm] debug stderr.write left in client.request().
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DirSize/MD5 return error 599 while the async task is still initialising on
the NAS, not only after the task is gone. Remove the 5-consecutive-599 abort
limit and the debug stderr logging; instead pass on 599 and keep polling
until the existing 60 s timeout fires. Rename the test that checked the old
limit to reflect the new timeout-based behaviour.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Logs [poll] lines to stderr so Claude Desktop's MCP log shows exactly
what DSM returns on each status call: attempt number, elapsed time,
finished flag, data keys (on success) or error code + message (on 599).
Version 0.2.3 — remove this logging once the 599 root cause is confirmed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DirSize for large directories (e.g. /docker, 8441 folders, 46832 files)
takes ~800ms to compute. While running, status returns intermediate
progress (finished=false). But on the very first poll the task can return
599 transiently (task just started, not yet available). Previously
_poll_task caught any SynologyError and returned immediately, making
dir_size always fail on the first 599.
Fix: treat 599 as a transient condition and continue polling. Give up
only after 5 consecutive 599 responses. All other error codes remain
immediately fatal.
Investigation confirmed with test_dirsize_md5.py:
- /test-mcp (2937 B): finished=true at 0ms
- /docker (3.9 GB, 46832 files): finished=false at 35ms, finished=true at 789ms
Tests: 2 new cases (retry-succeeds, 5x-599-gives-up) → 95 total
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Includes all DirSize/MD5 polling fixes:
- initial_delay=0.0 for DirSize and MD5 (poll immediately after start)
- MD5 status uses version=1 (v2 always returns 599 on this NAS)
- __version__ kept in sync with pyproject.toml
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>