<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:media="http://search.yahoo.com/mrss/"
>

<channel>
	<title>pseudo-terminal &#8211; Wade Tregaskis</title>
	<atom:link href="https://wadetregaskis.com/tags/pseudo-terminal/feed/" rel="self" type="application/rss+xml" />
	<link>https://wadetregaskis.com</link>
	<description></description>
	<lastBuildDate>Mon, 23 Mar 2026 01:03:54 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://wadetregaskis.com/wp-content/uploads/2016/03/Stitch-512x512-1-256x256.png</url>
	<title>pseudo-terminal &#8211; Wade Tregaskis</title>
	<link>https://wadetregaskis.com</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">226351702</site>	<item>
		<title>SSH waits for background jobs to exit only in non-pseudo-terminal mode</title>
		<link>https://wadetregaskis.com/ssh-waits-for-background-jobs-to-exit-only-in-non-pseudo-terminal-mode/</link>
					<comments>https://wadetregaskis.com/ssh-waits-for-background-jobs-to-exit-only-in-non-pseudo-terminal-mode/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Sun, 22 Mar 2026 23:40:42 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[pseudo-terminal]]></category>
		<category><![CDATA[SSH]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8867</guid>

					<description><![CDATA[Ugh, this one caused me days of head-scratching. If you run that command sequence, you&#8217;ll find that it makes the SSH connection and then exits immediately &#8211; and it kills the background job (sleep 15) as it exits. Which I find intuitive if only because that&#8217;s obviously how it&#8217;s always worked. But now run it&#8230; <a class="read-more-link" href="https://wadetregaskis.com/ssh-waits-for-background-jobs-to-exit-only-in-non-pseudo-terminal-mode/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>Ugh, this one caused me days of head-scratching.</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>$ ssh somehost
somehost$ sleep 15 &amp;
somehost$ exit</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000">$ </span><span style="color: #A31515">ssh</span><span style="color: #000000"> </span><span style="color: #A31515">somehost</span></span>
<span class="line"><span style="color: #000000">somehost$ </span><span style="color: #A31515">sleep</span><span style="color: #000000"> </span><span style="color: #098658">15</span><span style="color: #000000"> &amp;</span></span>
<span class="line"><span style="color: #000000">somehost$ </span><span style="color: #A31515">exit</span></span></code></pre></div>



<p>If you run that command sequence, you&#8217;ll find that it makes the SSH connection and then exits immediately &#8211; and it kills the background job (<code>sleep 15</code>) as it exits.  Which I find intuitive if only because that&#8217;s obviously how it&#8217;s always worked.</p>



<p>But now run it this way:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>$ ssh somehost 'sleep 15 &amp;'</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000">$ </span><span style="color: #A31515">ssh</span><span style="color: #000000"> </span><span style="color: #A31515">somehost</span><span style="color: #000000"> </span><span style="color: #A31515">&#39;sleep 15 &amp;&#39;</span></span></code></pre></div>



<p>Now it makes the SSH connection, <em>waits fifteen seconds</em>, and only <em>then</em> exits.</p>



<p>This subtle difference matters a lot if you&#8217;re e.g. writing an automation system that happens to sometimes spawn background jobs on remote machines via SSH, since it can lead to SSH commands hanging (potentially forever, depending on the nature of the background jobs).</p>



<p>And it&#8217;s <em>very</em> hard to debug, because no matter what debugging aids you put into your SSH session&#8217;s remote command &#8211; e.g. an explicit <code>exit 0</code> at the end, <code>set -xv</code>, or <code>echo 'Okay, going away now!'</code> as the last command &#8211; it&#8217;ll still just hang instead of completing.</p>



<p>Turns out this is because when run <em>without</em> a remote command, SSH implicitly creates a pseudo-terminal.  And the <em>pseudo-terminal</em> provides the behaviour of killing all subprocesses (including un-detached background jobs) when the main process ends.</p>



<p>When you run SSH <em>with</em> a remote command, SSH does <em>not</em> create a pseudo-terminal.  I&#8217;m guessing technically what it&#8217;s doing is waiting for the remote side to close the connection, which the remote side (sshd) won&#8217;t do until <em>all</em> child processes have exited (or at least closed their stdin, stdout, and stderr?).</p>



<p>One workaround is to use the <code>-t</code> argument to SSH, to force creation of a pseudo-terminal.  The downside is that remote programs may then assume an interactive session, so they may prompt for input in cases where they wouldn&#8217;t otherwise.  That could cause misalignment between what you write through to the remote command and what it&#8217;s expecting input for, or may cause a hang (if you don&#8217;t write anything but keep the remote side&#8217;s stdin open), or may cause the remote command to abort because it detects the end of stdin.</p>



<p>Another option is to manually kill all background jobs before exiting the main command, with e.g.:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>jobs -p | egrep '^\d' | xargs kill</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #795E26">jobs</span><span style="color: #000000"> </span><span style="color: #0000FF">-p</span><span style="color: #000000"> | egrep </span><span style="color: #A31515">&#39;^\d&#39;</span><span style="color: #000000"> | xargs </span><span style="color: #A31515">kill</span></span></code></pre></div>



<p>…or just kill <em>all</em> subprocesses:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000">pkill </span><span style="color: #0000FF">-P</span><span style="color: #000000"> </span><span style="color: #0000FF">$$</span></span></code></pre></div>



<p>Or, if you can precisely control where you create background jobs (<em>and</em> are confident they don&#8217;t spawn background jobs recursively), you can do:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>some background command &amp;
BACKGROUND_PID=$!

… # Rest of script.

kill ${BACKGROUND_PID}
# *Now* we can exit without hanging.</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000">some </span><span style="color: #A31515">background</span><span style="color: #000000"> </span><span style="color: #A31515">command</span><span style="color: #000000"> &amp;</span></span>
<span class="line"><span style="color: #001080">BACKGROUND_PID</span><span style="color: #000000">=</span><span style="color: #0000FF">$!</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">… </span><span style="color: #008000"># Rest of script.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #795E26">kill</span><span style="color: #000000"> ${BACKGROUND_PID}</span></span>
<span class="line"><span style="color: #008000"># *Now* we can exit without hanging.</span></span></code></pre></div>



<p>Just beware with this more precise approach that it&#8217;s more fragile &#8211; subprocesses could get spawned in ways you didn&#8217;t anticipate (if not now, then maybe in future versions of your code / production environment), and that sometimes the PID returned is effectively invalid because it&#8217;s merely the PID of some transient subprocess anyway (not the <em>long-lived</em> subprocess(es)).</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/ssh-waits-for-background-jobs-to-exit-only-in-non-pseudo-terminal-mode/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8867</post-id>	</item>
	</channel>
</rss>
