expect Error: 'send: spawn id exp4 not open' — Cause, Fix, and Troubleshooting Guide
Fix expect's 'send: spawn id exp4 not open while executing send' — the spawned process hit EOF or died; match prompts before send and handle eof/timeout.
- #automation
- #troubleshooting
- #expect
Fixing errors like this? Get 500 free DevOps AI prompts
500 copy-paste AI prompts for the stack you actually run — one PDF, free.
Overview
expect automates interactive programs by spawning a process, waiting for expected output, and sending input in response. Each spawned process gets a “spawn id” — a handle to its pseudo-terminal. This error means your script tried to send to a spawn id whose process has already closed (hit EOF), so there is nothing on the other end to receive the input:
$ expect deploy.exp
spawn ssh myuser@example.com
myuser@example.com's password:
Permission denied, please try again.
send: spawn id exp4 not open
while executing
"send "systemctl restart app\r""
(file "deploy.exp" line 14)
The trace is unusually helpful: it names the spawn id (exp4), the exact send that failed, and the line number. The root story is almost always the same — the spawned program exited (a failed login, a dropped connection, a crash) before your script’s next send, usually because a preceding expect matched the wrong thing or timed out and let the script race ahead to a send that has no live process to talk to.
Symptoms
send: spawn id expN not open while executing "send ..."with a file and line number.- The failure follows an unexpected line in the session output (
Permission denied,Connection closed, a shell that exited). - The script works interactively or against one host but fails intermittently against slower or flaky hosts.
- A missing
expect eofat the end, or asendplaced after the remote session has already ended. - Behavior is timing-dependent — it passes when the remote responds quickly and fails when it lags.
Common Root Causes
1. The spawned process died before the send
The most common cause. The program you spawned exited — a wrong SSH password (Permission denied), a rejected key, a remote command that terminated the shell, or a crash — so the next send hits a closed spawn id.
2. A preceding expect matched the wrong pattern or timed out
Your expect waited for a prompt that never came, timed out after the default 10 seconds, and fell through to the send. The process may still be alive but not at the state you assumed — or it closed during the wait.
3. No prompt matching before send (a send race)
The script sends input without first expecting the prompt that signals readiness. It fires input at a process that has not reached (or has already left) the expected state.
4. The session was closed by timeout
A long-running remote command exceeded timeout, expect gave up, and control moved to a send targeting a session the far side already tore down.
5. SSH / FTP / telnet disconnected mid-script
The transport dropped — network blip, server-side idle timeout, Connection closed by remote host — leaving the spawn id closed before the next interaction.
6. Missing expect eof, so the script exits or sends past process end
Without expect eof (or a wait), the script does not synchronize with the process ending and may attempt to send after it has closed.
How to Diagnose
Turn on both internal and user-visible logging so you can see exactly what the process emitted and when the spawn id closed:
# In the script, near the top:
# exp_internal 1
# log_user 1
expect -d deploy.exp 2>&1 | tail -30
expect: does " ...password: " (spawn_id exp4) match glob pattern "*$ "? no
expect: read eof
expect: set expect_out(spawn_id) "exp4"
send: spawn id exp4 not open
read eof immediately before the failing send is the definitive signal: the process closed while expect was still waiting for a prompt that never arrived. Confirm what the process actually printed versus what your pattern expected:
grep -nE 'expect|send' deploy.exp
9: expect "*$ "
12: expect "password:"
13: send "$PASSWORD\r"
14: send "systemctl restart app\r"
Line 14 sends without an intervening expect — a send race. And line 12/13 has no branch for a failed password, so a Permission denied closes the session and line 14 fires into a dead spawn id. Reproduce against a deliberately failing target to watch the sequence:
PASSWORD=wrong expect -d deploy.exp 2>&1 | grep -E 'Permission|eof|not open'
Permission denied, please try again.
expect: read eof
send: spawn id exp4 not open
Fixes
Match a prompt before every send, so you never send into a process that has not reached the expected state:
spawn ssh myuser@example.com
expect {
"password:" { send "$PASSWORD\r" }
"yes/no" { send "yes\r"; exp_continue }
timeout { puts "no password prompt"; exit 1 }
eof { puts "connection closed before login"; exit 1 }
}
# Only send the command after confirming we reached a shell prompt
expect {
-re {[$#] $} { send "systemctl restart app\r" }
"Permission denied" { puts "auth failed"; exit 1 }
timeout { puts "no shell prompt"; exit 1 }
eof { puts "session ended before prompt"; exit 1 }
}
Handle eof and timeout explicitly in every expect block. These are the two ways a session ends unexpectedly, and catching them turns a cryptic “spawn id not open” into a clear, actionable exit:
set timeout 30
expect {
"COMPLETE" { puts "done" }
timeout { puts "timed out waiting for COMPLETE"; exit 1 }
eof { puts "process exited early"; exit 1 }
}
Guard the send itself when a process might legitimately have closed, so a race degrades to a clean error instead of a stack trace:
if {[catch {send "systemctl restart app\r"} err]} {
puts "send failed: $err"
exit 1
}
Synchronize with process end using expect eof (and optionally wait to collect the exit status) instead of letting the script run off the end:
send "exit\r"
expect eof
lassign [wait] pid spawnid os_error exit_code
puts "remote exited with $exit_code"
Raise the timeout for genuinely slow steps rather than letting the default 10 seconds fall through to a send:
set timeout 120
expect "Deployment finished"
Verify the hardened script against both the happy path and the failing path:
PASSWORD=correct expect deploy.exp; echo "exit=$?"
PASSWORD=wrong expect deploy.exp; echo "exit=$?"
done
exit=0
auth failed
exit=1
What to Watch Out For
- “spawn id not open” is a symptom, not the cause. The real problem is the earlier
expectthat matched the wrong thing or timed out — read the lines above the failingsend, especially anyread eof. - Never
sendwithout a precedingexpectfor the prompt that signals readiness. A send race passes on fast hosts and fails on slow ones, making it look flaky when it is deterministic given timing. - Always add
timeoutandeofbranches. Their absence is why so many expect scripts fail with this exact error the first time a login is rejected or a link drops. - The default
timeoutis 10 seconds. Any step that can legitimately take longer — a build, a package install, a slow login banner — needs an explicit, largerset timeout. - End interactive sessions with
expect eof(andwaitif you need the exit code). Exiting the script without it can leave the child in an ambiguous state and mask a non-zero remote exit.
Related Guides
- envsubst Error: command not found
- flock Error: failed to execute — No such file or directory
- Scheduled Job Orchestration at Scale
Fixed it? Get 500 Automation & DevOps AI prompts — free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.
Did this fix your issue?
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.