fix(daemon): no terminal on the install container, and SteamCMD is why - #123
Merged
Conversation
A Garry's Mod installation ended on
Connecting anonymously to Steam Public...OK
Waiting for user info...OK
ERROR! Failed to install app '4020' (Missing configuration)
and exit 8, before a byte of depot. It was not this machine's doing and it
was not a passing Steam mood: it was the tty the daemon gave the install
container.
Measured eight times on one machine, interleaved so that neither time nor a
Steam outage can explain it — seven failures out of seven with a tty, three
successes out of three without. `TERM` is not the variable: `TERM=xterm`
without a tty downloads, an empty `TERM` with one fails. And nothing a script
can do gets out from under it — stdin from /dev/null, stdout down a pipe, and
`setsid` to drop the controlling terminal were all still refused.
It is not one game either. 104 of the 274 published eggs read for this
catalogue install from SteamCMD, so a tty was a wall in front of every one of
them, carrying a message that names neither the panel nor the terminal.
So the install container is created with `Tty: false`. What that costs is the
attach stream, which is then multiplexed rather than plain: eight bytes of
header before each piece of output, frames that straddle socket reads, and
stdout and stderr interleaved. `DockerFrameReader` reads it back into the one
text stream a tty used to give, decoding through a StringDecoder — which also
closes the seam `chunk.toString('utf8')` had at every chunk boundary, where a
multi-byte character split in two came out as two replacement characters.
Its fallback is deliberate and narrow: a header that makes no sense turns the
rest of the stream into plain text. It should never happen, since the daemon
creates the containers it attaches to — but an install console filling with
control characters is the worst failure available here, on the screen
somebody is watching to find out why their installation is stuck. The
installer's own tests now emit framed output, so they exercise the path that
runs rather than that fallback; breaking the reader fails eight unit tests
and one installer test.
The Source templates gain a retry, which is a different question and a
smaller one: 6.87 GB is a long time for a connection to stay up, and Steam
resumes from what it has staged under steamapps/downloading. Three attempts,
then a diagnosis, because none of the three fixes is guessable from what
SteamCMD prints — `Missing configuration` now means this node's daemon is
older than the panel that sent the script, `state is 0x202` means the volume
is out of room (measured by giving it 1.9 GB for a 6.87 GB depot), and `No
subscription` means the app id is not anonymously installable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ReX2JSG6VKYShanE1F69KP
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The report
A Garry's Mod install, twice, ending on:
What it is
The tty the daemon gives the install container.
Measured on one machine, interleaved so that neither time nor a Steam outage can explain it:
-tMissing configuration, 7 runs out of 7-tTERMis not the variable —TERM=xtermwithout a tty downloads, an emptyTERMwith one fails. Nothing a script can do escapes it either: stdin from/dev/null, stdout down a pipe, andsetsidto drop the controlling terminal were all still refused. Ruled out along the way: the node's own configuration (allowedOrigins, MTU,enable_icc), the capability set, the memory and pid limits, and disk space — 1.9 GB for a 6.87 GB depot givesstate is 0x202, which is a different message.This is not one game. 104 of the 274 published eggs read for this catalogue install from SteamCMD. A tty was a wall in front of every one of them, with a message that names neither the panel nor the terminal.
What changes
Tty: falseon the install container.The cost is the attach stream, which is then multiplexed rather than plain: eight bytes of header before each piece of output, frames that straddle socket reads, stdout and stderr interleaved.
DockerFrameReaderreads it back into the one text stream a tty used to give. It decodes through aStringDecoder, which also closes a seam that was already there —chunk.toString('utf8')split a multi-byte character into two replacement characters at every chunk boundary.Its fallback is narrow and deliberate: a header that makes no sense turns the rest of the stream into text. It should never happen, since the daemon creates the containers it attaches to — but an install console filling with control characters is the worst failure available on the screen somebody is watching to find out why their install is stuck.
The installer's tests now emit framed output, so they exercise the path that runs rather than that fallback. Verified by mutation: making the reader pass bytes through fails eight unit tests and one installer test.
And a retry, which is a different question
6.87 GB is a long time for a connection to stay up, and Steam resumes from what it has staged under
steamapps/downloading. So the Source install makes three attempts, then names which of three things happened:Missing configurationstate is 0x202No subscriptionThat first row is the version-skew case this fix creates: the panel sends the script, the daemon creates the container, and a node that has not been updated still has the tty.
Replaces #122
That PR blamed a passing Steam mood, on a comparison with an uncontrolled variable — the two containers differed by a tty, not only by ten minutes. Closed with the measurements.
🤖 Generated with Claude Code