an unrecognised command holds the exec session open for stdin - #20
Merged
Conversation
Closes #18. The interpreter answers a known verb and is done, which is right — echo, cat and rm have said everything they are going to say. An unknown command is a different situation: a real sprite would have started a process the caller can now write to, and callers do. fountain opens exec for its runtime command and then writes the prompt in as stdin, so exiting immediately meant that write landed on a process already gone, and its conversation server crashed on a call to a dead pid rather than getting an error back. So an unrecognised command now stays open and echoes stdin back on stdout until StreamStdinEOF or close. Still not execution — the same echo the interpreter already does, extended over time. Deliberately narrow, per the discussion on #18: known verbs keep exiting immediately and every existing client, including chant's Fly activities, sees exactly today's semantics. A test asserts that separately from the new path so the two cannot drift. Verified against a real fountain deployment on k3d. Before, every turn ended reattach interrupted {"reason":"no_active_session","outcome":"turn_orphaned"} Now the turn runs to completion: stage turn started output claude --dangerously-skip-permissions --print --verbose ... output say hello stage turn done {"exit_code":0} The reply is the prompt echoed back, which is what an emulator should produce and what the downstream docs already say a local conversation proves. It is the difference between fountain-ops provisioning a sandbox and fountain-ops holding a conversation offline. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WPvVBaE97cfmvzytEvJCBr
jhgaylor
added a commit
that referenced
this pull request
Aug 7, 2026
#19 and #20 both merged after v0.4.1 was tagged, so no released image carries either. Neither had a changelog entry, and `just release` refuses without a `## [0.5.0]` section — this adds it so the tag can be cut. Minor rather than patch: an unupgraded GET on the exec path is new behavior, and holding an unrecognised command open changes exec's lifetime for the clients that hit that branch. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Closes #18. The narrow option from that thread, approved: known verbs are untouched, only unrecognised commands hold open.
Why
The interpreter answers a known verb and is done —
echo,cat,rmhave said everything they are going to say. An unknown command is a different situation: a real sprite would have started a process the caller can now write to, and callers do. fountain opens exec for its runtime command and then writes the prompt in as stdin, so exiting immediately meant that write landed on a process already gone:Its conversation server crashed on a call to a dead pid rather than getting an error back.
What changed
An unrecognised command stays open and echoes stdin back on stdout until
StreamStdinEOFor close. Still not execution — the same echo the interpreter already does, extended over time.ExecResult.Unrecognisedis set by the interpreter's default branch, so the server can tell the two situations apart without guessing at the command string.Blast radius: none for existing clients
Known verbs exit immediately exactly as before, which is what chant's Fly activities depend on.
TestKnownVerbStillExitsImmediatelyasserts that separately from the new path so the two cannot drift.Verified against real fountain
On k3d, with this build swapped in. Before, every turn ended:
Now:
The reply is the prompt echoed back — what an emulator should produce, and what the downstream docs already say a local conversation proves. It is the difference between fountain-ops provisioning a sandbox and fountain-ops holding a conversation offline.
go test ./...andgo vet ./...green.