Summary

  • SSH_MSG_CHANNEL_WINDOW_ADJUST grants permission to send more bytes on an SSH channel; it does not say that a remote command consumed them or completed.
  • An exec request reply, an optional exit status and the close handshake describe different moments. None should silently substitute for an application’s durable result.
  • Consequential automation needs an operation identity and a way to query the remote postcondition, so an interrupted SSH session does not force a blind retry.

Four states hiding behind one green light

Remote-command software often wants one answer: did it work? RFC 4254 offers several answers to smaller questions. Was a channel opened? Did the server accept an exec request? May the sender place more bytes into the channel? Did the remote process report an exit status? Did both peers close their channel state?

Those are useful observations precisely because they are not the same observation. A request can be accepted and later fail. Input can move while a process is still running. A process can terminate after handing work to another service. A channel can disappear before the client receives the last message. Compressing all of that into a Boolean turns a layered protocol into an accidental business transaction system.

The safer model has four separate states: request admission, channel-flow credit, process termination and durable application outcome. The first three can be visible through SSH. The fourth belongs to the program or service being operated.

What the channel window actually grants

Section 5.2 of RFC 4254 defines the window in bytes. It limits how much channel data the other party may send before waiting. SSH_MSG_CHANNEL_WINDOW_ADJUST carries a recipient channel number and a number of bytes to add. Once it arrives, the peer may send that much more than its previous allowance. Ordinary data and extended data such as standard error consume the same window.

That is a flow-control contract. Its vocabulary is permission, quantity and channel. The message has no command identifier, application status, persistence marker or result digest. The specification also does not require one internal buffering event to precede an adjustment. An implementation may connect window management to its own memory and scheduling design.

It is therefore reasonable to infer that the peer is prepared for additional channel traffic. It is not reasonable to infer that a particular byte has crossed every boundary between the SSH stack, the operating system, the invoked process and the system that process may call. The window keeps a sender from overrunning agreed channel capacity. It is not an acknowledgement ledger.

Admission is not completion

RFC 4254 makes a separate mechanism for channel-specific requests. A request can set want reply; the recipient then answers with channel success, channel failure or a request-specific continuation. For exec, the request asks the server to start execution of the supplied command, and the RFC recommends requesting and checking the reply.

That reply matters. It distinguishes a server that accepted the start request from one that rejected or did not support it. But the command has only entered its lifecycle. Channel success does not contain the command’s later exit code or the durable object an application might create.

This distinction is easy to lose in libraries that expose callbacks such as “ready,” “sent” or “success” without making the protocol layer visible. The name can sound conclusive while describing only request admission. Operators should record what the event actually attests and which identifier ties it to the session and command.

Exit status is stronger—and still bounded

RFC 4254 provides an exit-status message for the point after the command at the other end terminates. Returning it is recommended, not mandatory. No acknowledgement is sent for that message, and a client may ignore it. The text says that zero usually means successful termination.

An exit status is therefore stronger evidence than channel flow. It comes from the command lifecycle rather than the byte budget. For a conventional, synchronous program whose contract is “exit zero after completing the requested change,” it may be the right outcome signal.

But “usually” and the application contract still matter. A wrapper can return zero after enqueueing work. A service manager can accept a restart request before health is restored. A deployment command can update a control plane while replicas converge later. SSH correctly carries the process result; it cannot manufacture the semantics of the program being run.

Close ends channel state, not ambiguity

EOF and close are also deliberately narrow. EOF says that one side will send no more data; it has no explicit response and does not close the reverse direction. Either party may send close without first sending or receiving EOF. A party considers the channel closed after it has both sent and received close, and earlier data should be delivered to the destination if possible.

This is orderly resource lifecycle management. It is not a guarantee that every prior byte caused an application effect, nor does a missing final message prove that the effect did not happen. A network break at the wrong instant can leave the classic uncertain outcome: the remote program committed the change, but the client never received the evidence.

The worst response is to translate uncertainty into automatic failure and repeat the command without knowing whether repetition is safe.

Security guarantees do not become outcome guarantees

The architecture around RFC 4254 reinforces the separation. RFC 4251 divides SSH into transport, user authentication and connection protocols. RFC 4253 protects the transport with encryption, server authentication and integrity. RFC 4252 authenticates the client-side user. The connection protocol then multiplexes logical channels over those foundations.

These properties answer critical questions: which host is at the far end, which user was authenticated, and whether data was protected in transit. They do not answer whether a package install reached its postcondition, whether a database migration committed, or whether a remote API accepted a delegated request. Strong authentication makes the evidence attributable. It does not broaden what the evidence says.

Give the operation a life outside the session

For an irreversible or expensive action, the application-level operation should have an identity that survives the SSH connection. The remote side can record the authenticated principal, target, command or request fingerprint, admission time, current state, final result and any durable object ID. The caller needs a query path for that record after reconnecting.

This need not be a new SSH extension. It can be a command-line contract: submit an idempotency key, receive an operation ID, and query that ID until the application reaches a terminal state. A simple local command may need nothing more than a checked exit status. The stronger pattern belongs where a repeated action could duplicate money movement, rotate credentials twice, interrupt a fleet or create two resources.

The governing rule is modest: call each signal by its proper name. Window adjustment is byte credit. Request success is admission. Exit status is process evidence. Channel close is connection-layer finality. Business completion must be supplied by the business operation.

Sources