Process result does not describe the whole workflow
The lab compares two sequences that try to retrieve a missing file before uploading another. Without a prefix, get fails, the client exits one and the later upload creates no file. With -get, the error is ignored for continuation, the later put runs and the process exits zero. The fictional requirement makes the read mandatory, so the second workflow remains invalid. Record results by stage and make scheduler state represent actual dependencies instead of relying only on the last completed command.
Recognize a publication failure
Another test server starts with a policy refusing rename and posix-rename. Put creates policy.part and its hash matches the source. Publication fails, the process exits one and policy.bin does not appear. This result does not require assuming corruption or resending every byte. Complete staging exists that needs reconciliation, and a refused operation needs investigation. Retain the consumer boundary: making it read any partial to work around failure can expose other transfers that are still incomplete.
Distinguish subsystem policy from permissions
In a directory writable by the local process, sftp-server -R permits get and refuses put. The comparison retains the filesystem and demonstrates an additional subsystem restriction. Indiscriminately changing permissions does not resolve that policy. Also confirm which executable interprets each argument: -R on the server means read only, while -R on the client configures outstanding requests and takes a number. In a ticket, retain the full invocation with arguments associated with the correct component. An isolated letter does not identify the control causing failure.
The mask is not always the last control
The experiment source has mode 0644 and the server uses umask 0077. A normal put leaves the new file at 0600. With put -p, attribute preservation finishes at 0644. Creation and later attribute change are distinct moments. If the contract requires reading only by the service account, check final state and review preservation, source mode and authorized destination policy. The experiment uses files owned by the current user: it demonstrates neither ACLs, ownership across accounts, partner isolation nor chroot requirements.
Bound versions and reproduction
Executed commands are /usr/bin/sftp and /usr/libexec/sftp-server, identified by hashes in the evidence. SSH -V on the same machine reports OpenSSH 10.3p1; the two SFTP executables provided no separate product version. Consulted official notes present 10.5p1, which was not executed in this review. The local protocol announces version three and extensions observed in logs. The runner uses only synthetic temporary files and removes them afterwards. Direct connection does not validate keys, credentials, encryption or international-network characteristics.
Hand RUN a recoverable state
Replace the vague label “SFTP OK” with stages and evidence: manifest reading, transfer, validation, publication and acceptance according to the contract. Record names, version, identifier, outcome, remaining partial and recovery procedure. If a mandatory operation failed, prevent dependent advancement and communicate the at-risk deadline. To authorize the change, test the actual scheduler client and destination controls. The summary is that technical transfer completion can coexist with refused publication, unsuitable permissions or missing functional validation.
# From the project root; macOS bundled client/server paths are explicit
python3 content/labs/sftp-delivery/run.py
# -D executes the local SFTP subsystem directly, without SSH
# criticalAbort / ignoredError: inspect stage outcome and side effects
# renameDenied: complete partial remains; final name absent
# preservedPermissions: put 0600; put -p 0644 with server umask 0077Fictional case: the mandatory manifest fails under -get, but a later upload makes the process green. RUN corrects advancement conditions and treats the delivery as pending.
Common pitfalls
Using zero exit as fulfillment of every prerequisite, resending bytes to fix refused rename, or assuming umask enforces final mode after put -p.
Related topics: Batch and observable failures · File publication and resumption · Diagnosis and RUN handover
Recovery should start from the failed stage and actual existing state, retaining controlled publication, permissions and delivery references.
Reference: OpenSSH SFTP subsystem · BigSavant SFTP 2026-09; selected OpenSSH client, server and extension behavior