CI Automation
Some programs require interactive input even in CI — they prompt for confirmations, display menus, or wait for keystrokes that have no non-interactive equivalent. hty lets you script those interactions precisely: start the program, wait for each prompt, send the right input, and verify the output.
Basic CI pattern
A CI script that drives an interactive program is a sequence of “wait for this prompt, type that response.” hty’s fused --snapshot --wait-until-* flags collapse each pair into a single command, which keeps the script linear and easy to read:
#!/usr/bin/env bash
set -e
# Clean up the session when the script exits (success or failure)
trap 'hty kill auth-flow --delete 2>/dev/null || true' EXIT
# Launch gh auth login and wait for the first prompt
hty run --name auth-flow --snapshot \
--wait-until-text "What account" --timeout 10000 \
-- gh auth login
# Select GitHub.com (Enter), wait for the authentication method prompt
hty send auth-flow --key enter --snapshot \
--wait-until-text "How would you like to authenticate" --timeout 5000
# Choose "Paste an authentication token", wait for the token prompt
hty send auth-flow --text "2\n" --snapshot \
--wait-until-text "Paste your authentication token" --timeout 5000
# Send the token and wait for the program to exit
hty send auth-flow --text "${GITHUB_TOKEN}\n" --wait-until-exit --timeout 15000The trap at the top ensures the session is always cleaned up, even if the script exits early due to set -e. hty kill --delete terminates the process and removes the record + log in one shot, which is almost always what you want in CI.
Handling failures
When a wait times out, hty wait (and the fused form) exits with code 3. Capture the screen at that moment so you have a record of what the program was showing:
if ! hty wait session --text "Success" --timeout 30000; then
echo "=== Session screen at failure ==="
hty snapshot session --json | jq -r '.snapshot.buffer'
hty kill session --delete
exit 1
fiThe snapshot captures the exact state of the terminal at the moment of failure — the most useful artifact you can have when diagnosing a stuck CI run.
Session cleanup
Always delete sessions when you’re done in CI. Sessions persist in the hty server until explicitly removed, so failing to clean up can leave stale state.
Use a trap to guarantee cleanup regardless of how the script exits:
trap 'hty kill my-session --delete 2>/dev/null || true' EXIT
hty run --name my-session -- my-interactive-programThe hty server auto-shuts-down 10 seconds after all running sessions end. You don’t need to stop or clean up the server daemon in CI — it exits on its own once your sessions are gone.
Capturing artifacts for debugging
Every hty session is recorded. You can save that recording in two complementary formats depending on what you need.
Raw event log. The JSONL log is the canonical source of truth — every byte in, every byte out, with timestamps. Best for programmatic post-mortems:
hty logs my-session > session.jsonl
# Find the exit code
hty logs my-session | jq 'select(.kind == "exited")'Asciinema .cast. For a human-watchable artifact, export to asciinema v2:
hty export my-session --format asciicast > session.castA .cast file plays back in asciinema play, renders to a GIF with agg, and embeds inline in tools that support asciinema — including PR comments via asciinema upload. Uploading the .cast as a CI artifact lets reviewers watch what happened, which is dramatically more useful than scrolling JSONL when triaging a flaky interactive test.
# GIF for a PR comment or a dashboard
hty export my-session --format asciicast | agg - run.gifSet --timeout values conservatively large in CI. Network latency, cold container starts, and package installation can make programs significantly slower than on a developer laptop.