Skip to content

ssh: classify SFTP errors by typed StatusCode, not stringified message #173

Description

@zoza1982

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::NoSuchFileVfsError::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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions