Context
Follow-up from #172 (both bug-bot and code-review flagged it).
crates/cairn-backend-ssh/src/real.rs::map_err classifies SFTP errors by l-casing the stringified error and substring-matching ("no such file", "timeout", "connection", …). russh-sftp actually carries a typed russh_sftp::client::error::Error::Status { status_code: StatusCode, error_message }, so the current approach is fragile: any wording drift in a future russh-sftp release, or a server returning a differently-worded message for the same status, silently reclassifies errors (e.g. a real failure mapped to NotFound, or a NotFound mapped to a generic Backend).
This is what made #172's symptom server-dependent ("Failure" on OpenSSH vs "not found" on the reporter's DietPi box). #172's fix (create_dir stat-probes on failure) is deliberately insulated from map_err — it branches on the probe result, not the error type — so this is not urgent, but worth hardening.
Proposal
Match on the typed error where available:
Error::Status(s) if s.status_code == StatusCode::NoSuchFile → VfsError::NotFound
StatusCode::PermissionDenied → a dedicated permission error
- transport/
io variants → retryable Backend
- fall back to the current string heuristic only for untyped/opaque errors.
Note: OpenSSH's sftp-server speaks SFTP v3, whose StatusCode has no FILE_ALREADY_EXISTS (that's v6), so mkdir-on-existing stays a generic Failure — the #172 stat-probe remains necessary regardless.
Acceptance
map_err (or its replacement) matches typed StatusCode first, string heuristics last.
- Unit tests over the typed variants (mock/synthetic
Error::Status).
- No behavior regression in the
sftp_server_repro / copy_repro integration suites.
Context
Follow-up from #172 (both
bug-botandcode-reviewflagged it).crates/cairn-backend-ssh/src/real.rs::map_errclassifies SFTP errors by l-casing the stringified error and substring-matching ("no such file","timeout","connection", …). russh-sftp actually carries a typedrussh_sftp::client::error::Error::Status { status_code: StatusCode, error_message }, so the current approach is fragile: any wording drift in a future russh-sftp release, or a server returning a differently-worded message for the same status, silently reclassifies errors (e.g. a real failure mapped toNotFound, or aNotFoundmapped to a genericBackend).This is what made #172's symptom server-dependent ("Failure" on OpenSSH vs "not found" on the reporter's DietPi box). #172's fix (
create_dirstat-probes on failure) is deliberately insulated frommap_err— it branches on the probe result, not the error type — so this is not urgent, but worth hardening.Proposal
Match on the typed error where available:
Error::Status(s) if s.status_code == StatusCode::NoSuchFile→VfsError::NotFoundStatusCode::PermissionDenied→ a dedicated permission erroriovariants → retryableBackendNote: OpenSSH's
sftp-serverspeaks SFTP v3, whoseStatusCodehas noFILE_ALREADY_EXISTS(that's v6), somkdir-on-existing stays a genericFailure— the #172 stat-probe remains necessary regardless.Acceptance
map_err(or its replacement) matches typedStatusCodefirst, string heuristics last.Error::Status).sftp_server_repro/copy_reprointegration suites.