Route task-failure error codes through error_from_code (F18)#32
Conversation
Copy/move and delete background-task completion previously emitted a bare DSM_ERROR envelope when status carried an error dict, diverging from the synchronous paths that use synology_error_response / error_from_code. Both paths now route the DSM code through error_from_code(code, "SYNO.FileStation.CopyMove" | ".Delete") and use the mapped exception's error_code, retryable, and suggestion (falling back to the previous generic suggestion for unmapped codes). Behavior change: callers catching dsm_error on these two paths now receive the more specific envelope (e.g. not_found, already_exists, disk_full, filestation_error, permission_denied). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
Starting QA review — adding QA Active. CI fully green (lint, mypy, pytest 3.11/3.12/3.13, vdsm integration, version-sync, validate-server-json, on-push). Will run the test plan locally against this branch and re-verify the six new/updated test cases per the PR body. |
cmeans
left a comment
There was a problem hiding this comment.
QA Review — PR #32
Verdict: pass — zero findings. Ready to apply Ready for QA Signoff.
Verification (re-run locally on feat/task-error-from-code @ HEAD)
| Check | Result |
|---|---|
uv run ruff check src/ tests/ |
clean |
uv run ruff format --check src/ tests/ |
66 files already formatted |
uv run mypy src/ (strict) |
clean, 27 files |
uv run pytest |
499 passed, 94 deselected, 96.04% coverage (95% gate) |
| Targeted run of the 2 updated + 4 new task-error cases | 6/6 pass |
| CI (all jobs) | green (lint, mypy, pytest 3.11/3.12/3.13, vdsm integration, version-sync, validate-server-json, on-push) |
Deselected (94): -m 'not integration and not vdsm' from pyproject.toml addopts — integration/vdsm markers are intentionally excluded from the default suite and run in CI's separate vdsm integration tests job (passing here). Not a silent skip.
Code review
operations.py:256/operations.py:365— both task-completion sites correctly callerror_from_code(err_code, "SYNO.FileStation.CopyMove" | "SYNO.FileStation.Delete"). Signature verified againstcore/errors.py:264(error_from_code(code: int, api_name: str = "")). Note: issue #30's code snippet had the args reversed (error_from_code("SYNO.FileStation.CopyMove", err_code)) — the PR uses the real signature. No bug.- Envelope fields —
mapped.error_code,mapped.retryable,mapped.suggestion or <generic fallback>all resolve correctly against theSynologyErrorhierarchy (core/errors.py:72–148). Message text is preserved verbatim (DSM error code {err_code} on path: {err_path}) — no caller-visible string break. - Issue-AC pushback on 1100 — PR correctly documents that
error_from_code(1100, "SYNO.FileStation.*")returnsFileStationError(envelopefilestation_error), not common-105'spermission_denied. 1100 is FileStation's filesystem-permissions error and should stayfilestation_error; broadening topermission_deniedwould be a different and larger change. The issue's "e.g. PERMISSION_DENIED for 1100" wording was illustrative, not load-bearing. Agreed — out of scope. - CHANGELOG — one
### Changedentry under## Unreleased, standard Keep-a-Changelog category, explicitly calls out the semantic change ("callers previously catchingdsm_erroron these two paths will now receive the more specific code").
Issue #30 AC coverage
- ✅ Both sites route through
error_from_code() - ✅ Suggestion from per-code mapping with generic fallback
- ✅ Unit tests assert specific envelope (1100 →
filestation_error, 408 →not_found, 416 →disk_full+retryable, 105 →permission_denied, 9999 →dsm_errorfallback) - ✅ CHANGELOG
## Unreleased→### Changedwith behavior-change call-out - ✅ Existing assertion changes enumerated (§"Test assertions that changed" in PR body — 2 changes, both in
test_copy/delete_task_completes_with_error; timeout/poll-error tests correctly left alone since those paths don't go througherror_from_code)
Scope of behavior change
Limited to the two background-task completion paths in operations.py. Synchronous paths already routed through synology_error_response() → error_from_code() — no second-order impact. Unknown codes still produce dsm_error (explicit test: test_copy_task_error_unknown_code_falls_back).
Clean, surgical F18 fix. Applying Ready for QA Signoff as my final act.
Summary
operations.py:256(copy/move) andoperations.py:365(delete) previously emitted a bareDSM_ERRORenvelope when the background task completed with anerrordict, diverging from synchronous paths in the same module that already route througherror_from_code()viasynology_error_response().error_from_code(err_code, "SYNO.FileStation.CopyMove" | ".Delete")and use the mapped exception'serror_code,retryable, andsuggestionfor the envelope (with the previous generic suggestion as fallback).Behavior change
Callers catching
dsm_erroron these two paths will now receive the more specific code. This is the exact change QA flagged as wanting its own review.dsm_errornot_founddsm_erroralready_existsdsm_errordisk_full(retryable=true)dsm_errorinvalid_parameterdsm_errorfilestation_errordsm_errorpermission_denieddsm_errordsm_error(unchanged)Note on issue AC wording: the issue body uses "e.g.
PERMISSION_DENIEDfor 1100" as an example, buterror_from_code(1100, "SYNO.FileStation.*")returnsFileStationError(envelopefilestation_error) — 1100 is FileStation's "insufficient filesystem permissions," not common code 105. The mapping remains more specific thandsm_error; broadening 1100 →permission_deniedis out of scope (it would affect synchronous paths across every FileStation module). Raised for visibility.Changes
src/mcp_synology/modules/filestation/operations.pyerror_from_code; routeerr_codethrough it at both task-completion sites; usemapped.error_code,mapped.retryable,mapped.suggestion or <generic fallback>. Message text unchanged (preserves theDSM error code {err_code} on path: {err_path}format).tests/modules/filestation/test_operations.pytest_copy_task_completes_with_error+test_delete_task_completes_with_errorto assertfilestation_erroron code 1100 and the per-code suggestion text. Added four new cases: 408→not_found(copy), 416→disk_full+retryable=true(copy), 105→permission_denied(delete), and an unknown-code (9999) fallback case confirmingdsm_error+ generic suggestion.CHANGELOG.md### Changedentry under## Unreleased(#30).Test assertions that changed
Every existing assertion is enumerated here per issue AC:
test_copy_task_completes_with_errorbody["error"]["code"] == "dsm_error"→"filestation_error""shared folder" in body["error"]["suggestion"].lower()(per-code mapping now wins).test_delete_task_completes_with_errorbody["error"]["code"] == "dsm_error"→"filestation_error"No other existing assertions changed. The timeout / poll-error tests are untouched (those paths do not go through
error_from_code).Test plan
uv run ruff check src/ tests/— cleanuv run ruff format --check src/ tests/— cleanuv run mypy src/— clean (strict)uv run pytest— 499 passed, 96.04% coverage (95% gate)Out of scope
synology_error_response/error_from_code🤖 Generated with Claude Code