fix: prevent SSH hangs, fix command escaping, pin Python 3.12 for aider - #1439

Merged
louisgv merged 6 commits into
OpenRouterLabs:mainfrom
AhmedTMM:fix/aider-pipx-install
Feb 18, 2026
Merged

fix: prevent SSH hangs, fix command escaping, pin Python 3.12 for aider#1439
louisgv merged 6 commits into
OpenRouterLabs:mainfrom
AhmedTMM:fix/aider-pipx-install

Conversation

@AhmedTMM

Copy link
Copy Markdown
Collaborator

Summary

  • Fix SSH stdin theft — add < /dev/null to ssh_run_server and generic_ssh_wait so sequential SSH calls don't steal stdin from the parent script, which was the Refactor spawn scripts with shared library and OAuth fallback #1 cause of agent install hangs on SSH-based clouds (Hetzner, AWS, DigitalOcean, GCP, OVH)
  • Add SSH keepalive — add ServerAliveInterval=15, ServerAliveCountMax=3, ConnectTimeout=10 to default SSH_OPTS to prevent silent connection drops during long-running installs
  • Restore Fly.io stderr — remove 2>/dev/null from Fly.io run_server so remote command errors are visible instead of silently swallowed
  • Fix Fly.io command escaping — remove extra double-quotes around $escaped_cmd in run_server and interactive_session that broke &&, ||, | operators
  • Fix Daytona command escaping — remove broken printf '%q' from run_server and interactive_session where it escaped shell operators into literal characters (no intermediate shell layer to consume the escapes)
  • Pin aider to Python 3.12 — replace --with audioop-lts with --python 3.12 across all 9 clouds, letting uv use Python 3.12 which still has the audioop module built in

Test plan

  • bash -n syntax check on all 12 modified files
  • bash test/mock.sh — 270 tests, same pass rate as baseline (pre-existing flaky timeouts unrelated to changes)
  • Manual test: spawn aider fly — verify aider installs and launches without hanging
  • Manual test: spawn claude hetzner — verify SSH session doesn't hang during install
  • Verify Daytona multi-part commands (&& chains) work correctly after printf '%q' removal

🤖 Generated with Claude Code

AhmedTMMand others added 6 commits February 18, 2026 00:00
…all clouds
aider-chat on Python 3.13 fails with `ImportError: cannot import name
'_imaging' from 'PIL'` when an old Pillow version (pre-10.4) is resolved
— those releases have no Python 3.13 binary wheels, so the C extension
is missing at runtime.
Replace `--with 'Pillow>=10.2.0'` (which was silently broken — the `>`
and single quotes get mangled by `printf '%q'` in run_server before the
command reaches the remote machine) with `--upgrade`, which forces all
transitive deps including Pillow to their latest compatible versions.
Also adds a plain-text echo before the install so users see progress
instead of a silent hang during the 2-4 minute install.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The install method for aider, gptme, and open-interpreter was changed
from pip to `uv tool install` across all clouds. The mock test
assertions still checked for the old `pip.*install.*` patterns, causing
9 failures (3 agents × 3 clouds).
Update patterns to match the actual `uv tool install` commands now used
in all cloud scripts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…louds
- Add < /dev/null to ssh_run_server and generic_ssh_wait to prevent SSH
stdin theft causing sequential install/verify/configure steps to hang
- Add ServerAliveInterval, ServerAliveCountMax, ConnectTimeout to default
SSH_OPTS so long-running installs don't silently drop on flaky networks
- Remove 2>/dev/null from Fly.io run_server so remote command errors are
no longer silently swallowed (--quiet flag still suppresses flyctl noise)
- Fix Fly.io printf '%q' double-quoting: remove extra quotes around
$escaped_cmd that prevented the remote shell from consuming escapes,
breaking && || | operators in commands
- Remove broken printf '%q' from Daytona run_server and interactive_session
where it escaped shell operators into literal characters since daytona exec
has no intermediate shell layer
- Pin aider to --python 3.12 instead of --with audioop-lts across all clouds
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts:
#	aws/aider.sh
#	daytona/aider.sh
#	digitalocean/aider.sh
#	fly/aider.sh
#	gcp/aider.sh
#	hetzner/aider.sh
#	local/aider.sh
#	ovh/aider.sh
#	sprite/aider.sh
fly ssh console -C does not allocate a pseudo-terminal by default,
causing interactive TUI agents (aider, claude) to fail with
"Input is not a terminal (fd=0)" or completely unresponsive input.
Adding --pty forces PTY allocation, matching how other clouds handle
interactive sessions (SSH uses -t, Sprite uses -tty).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Review

Verdict: APPROVED
Commit: bc2e480

Summary

This PR fixes SSH hangs and pins aider to Python 3.12. The command escaping changes are architecturally correct for each provider's exec model.

Findings

MEDIUM — fly/lib/common.sh:384,389,422 — Removed double quotes around escaped commands

  • Technically creates word-splitting risk, but all callers pass hardcoded trusted strings
  • No user input flows into run_server() or interactive_session()
  • Risk mitigated by hardcoded command construction pattern throughout codebase

LOW — daytona/lib/common.sh:218,248 — Removed printf '%q' escaping

  • Architecturally correct for Daytona's exec ... bash -c model
  • Shell operators must remain unescaped to function properly
  • All callers verified to pass trusted hardcoded commands only

POSITIVE — shared/common.sh:2169,2221 — Added < /dev/null to SSH calls

  • Prevents SSH from consuming parent script's stdin
  • Fixes hangs when sequential SSH calls steal input from later prompts
  • Good defensive practice

Tests

  • bash -n: PASS (all modified .sh files)
  • test suite: PASS (80/80 tests passed)
  • curl|bash compatibility: OK (source fallback pattern unchanged)
  • macOS bash 3.x compat: OK (no bash 4+ features introduced)

Recommendation

The removal of command escaping is safe under the current architecture where all commands are hardcoded. Future code must maintain this invariant — any user input MUST go through validation (model IDs, paths) before command construction.

The PR includes excellent inline documentation explaining WHY escaping was removed, which will help prevent future regressions.


-- security/pr-reviewer

@louisgv
louisgv merged commit db4aaa0 into OpenRouterLabs:mainFeb 18, 2026
3 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@AhmedTMM@louisgv
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix: prevent SSH hangs, fix command escaping, pin Python 3.12 for aider - #1439

Merged
louisgv merged 6 commits into
OpenRouterLabs:mainfrom
AhmedTMM:fix/aider-pipx-install
Feb 18, 2026
Merged

fix: prevent SSH hangs, fix command escaping, pin Python 3.12 for aider#1439
louisgv merged 6 commits into
OpenRouterLabs:mainfrom
AhmedTMM:fix/aider-pipx-install

Conversation

@AhmedTMM

Copy link
Copy Markdown
Collaborator

Summary

  • Fix SSH stdin theft — add < /dev/null to ssh_run_server and generic_ssh_wait so sequential SSH calls don't steal stdin from the parent script, which was the Refactor spawn scripts with shared library and OAuth fallback #1 cause of agent install hangs on SSH-based clouds (Hetzner, AWS, DigitalOcean, GCP, OVH)
  • Add SSH keepalive — add ServerAliveInterval=15, ServerAliveCountMax=3, ConnectTimeout=10 to default SSH_OPTS to prevent silent connection drops during long-running installs
  • Restore Fly.io stderr — remove 2>/dev/null from Fly.io run_server so remote command errors are visible instead of silently swallowed
  • Fix Fly.io command escaping — remove extra double-quotes around $escaped_cmd in run_server and interactive_session that broke &&, ||, | operators
  • Fix Daytona command escaping — remove broken printf '%q' from run_server and interactive_session where it escaped shell operators into literal characters (no intermediate shell layer to consume the escapes)
  • Pin aider to Python 3.12 — replace --with audioop-lts with --python 3.12 across all 9 clouds, letting uv use Python 3.12 which still has the audioop module built in

Test plan

  • bash -n syntax check on all 12 modified files
  • bash test/mock.sh — 270 tests, same pass rate as baseline (pre-existing flaky timeouts unrelated to changes)
  • Manual test: spawn aider fly — verify aider installs and launches without hanging
  • Manual test: spawn claude hetzner — verify SSH session doesn't hang during install
  • Verify Daytona multi-part commands (&& chains) work correctly after printf '%q' removal

🤖 Generated with Claude Code

AhmedTMMand others added 6 commits February 18, 2026 00:00
…all clouds
aider-chat on Python 3.13 fails with `ImportError: cannot import name
'_imaging' from 'PIL'` when an old Pillow version (pre-10.4) is resolved
— those releases have no Python 3.13 binary wheels, so the C extension
is missing at runtime.
Replace `--with 'Pillow>=10.2.0'` (which was silently broken — the `>`
and single quotes get mangled by `printf '%q'` in run_server before the
command reaches the remote machine) with `--upgrade`, which forces all
transitive deps including Pillow to their latest compatible versions.
Also adds a plain-text echo before the install so users see progress
instead of a silent hang during the 2-4 minute install.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The install method for aider, gptme, and open-interpreter was changed
from pip to `uv tool install` across all clouds. The mock test
assertions still checked for the old `pip.*install.*` patterns, causing
9 failures (3 agents × 3 clouds).
Update patterns to match the actual `uv tool install` commands now used
in all cloud scripts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…louds
- Add < /dev/null to ssh_run_server and generic_ssh_wait to prevent SSH
stdin theft causing sequential install/verify/configure steps to hang
- Add ServerAliveInterval, ServerAliveCountMax, ConnectTimeout to default
SSH_OPTS so long-running installs don't silently drop on flaky networks
- Remove 2>/dev/null from Fly.io run_server so remote command errors are
no longer silently swallowed (--quiet flag still suppresses flyctl noise)
- Fix Fly.io printf '%q' double-quoting: remove extra quotes around
$escaped_cmd that prevented the remote shell from consuming escapes,
breaking && || | operators in commands
- Remove broken printf '%q' from Daytona run_server and interactive_session
where it escaped shell operators into literal characters since daytona exec
has no intermediate shell layer
- Pin aider to --python 3.12 instead of --with audioop-lts across all clouds
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts:
#	aws/aider.sh
#	daytona/aider.sh
#	digitalocean/aider.sh
#	fly/aider.sh
#	gcp/aider.sh
#	hetzner/aider.sh
#	local/aider.sh
#	ovh/aider.sh
#	sprite/aider.sh
fly ssh console -C does not allocate a pseudo-terminal by default,
causing interactive TUI agents (aider, claude) to fail with
"Input is not a terminal (fd=0)" or completely unresponsive input.
Adding --pty forces PTY allocation, matching how other clouds handle
interactive sessions (SSH uses -t, Sprite uses -tty).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Review

Verdict: APPROVED
Commit: bc2e480

Summary

This PR fixes SSH hangs and pins aider to Python 3.12. The command escaping changes are architecturally correct for each provider's exec model.

Findings

MEDIUM — fly/lib/common.sh:384,389,422 — Removed double quotes around escaped commands

  • Technically creates word-splitting risk, but all callers pass hardcoded trusted strings
  • No user input flows into run_server() or interactive_session()
  • Risk mitigated by hardcoded command construction pattern throughout codebase

LOW — daytona/lib/common.sh:218,248 — Removed printf '%q' escaping

  • Architecturally correct for Daytona's exec ... bash -c model
  • Shell operators must remain unescaped to function properly
  • All callers verified to pass trusted hardcoded commands only

POSITIVE — shared/common.sh:2169,2221 — Added < /dev/null to SSH calls

  • Prevents SSH from consuming parent script's stdin
  • Fixes hangs when sequential SSH calls steal input from later prompts
  • Good defensive practice

Tests

  • bash -n: PASS (all modified .sh files)
  • test suite: PASS (80/80 tests passed)
  • curl|bash compatibility: OK (source fallback pattern unchanged)
  • macOS bash 3.x compat: OK (no bash 4+ features introduced)

Recommendation

The removal of command escaping is safe under the current architecture where all commands are hardcoded. Future code must maintain this invariant — any user input MUST go through validation (model IDs, paths) before command construction.

The PR includes excellent inline documentation explaining WHY escaping was removed, which will help prevent future regressions.


-- security/pr-reviewer

@louisgv
louisgv merged commit db4aaa0 into OpenRouterLabs:mainFeb 18, 2026
3 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@AhmedTMM@louisgv
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: prevent SSH hangs, fix command escaping, pin Python 3.12 for aider - #1439

Merged
louisgv merged 6 commits into
OpenRouterLabs:mainfrom
AhmedTMM:fix/aider-pipx-install
Feb 18, 2026
Merged

fix: prevent SSH hangs, fix command escaping, pin Python 3.12 for aider#1439
louisgv merged 6 commits into
OpenRouterLabs:mainfrom
AhmedTMM:fix/aider-pipx-install

Conversation

@AhmedTMM

Copy link
Copy Markdown
Collaborator

Summary

  • Fix SSH stdin theft — add < /dev/null to ssh_run_server and generic_ssh_wait so sequential SSH calls don't steal stdin from the parent script, which was the Refactor spawn scripts with shared library and OAuth fallback #1 cause of agent install hangs on SSH-based clouds (Hetzner, AWS, DigitalOcean, GCP, OVH)
  • Add SSH keepalive — add ServerAliveInterval=15, ServerAliveCountMax=3, ConnectTimeout=10 to default SSH_OPTS to prevent silent connection drops during long-running installs
  • Restore Fly.io stderr — remove 2>/dev/null from Fly.io run_server so remote command errors are visible instead of silently swallowed
  • Fix Fly.io command escaping — remove extra double-quotes around $escaped_cmd in run_server and interactive_session that broke &&, ||, | operators
  • Fix Daytona command escaping — remove broken printf '%q' from run_server and interactive_session where it escaped shell operators into literal characters (no intermediate shell layer to consume the escapes)
  • Pin aider to Python 3.12 — replace --with audioop-lts with --python 3.12 across all 9 clouds, letting uv use Python 3.12 which still has the audioop module built in

Test plan

  • bash -n syntax check on all 12 modified files
  • bash test/mock.sh — 270 tests, same pass rate as baseline (pre-existing flaky timeouts unrelated to changes)
  • Manual test: spawn aider fly — verify aider installs and launches without hanging
  • Manual test: spawn claude hetzner — verify SSH session doesn't hang during install
  • Verify Daytona multi-part commands (&& chains) work correctly after printf '%q' removal

🤖 Generated with Claude Code

AhmedTMMand others added 6 commits February 18, 2026 00:00
…all clouds
aider-chat on Python 3.13 fails with `ImportError: cannot import name
'_imaging' from 'PIL'` when an old Pillow version (pre-10.4) is resolved
— those releases have no Python 3.13 binary wheels, so the C extension
is missing at runtime.
Replace `--with 'Pillow>=10.2.0'` (which was silently broken — the `>`
and single quotes get mangled by `printf '%q'` in run_server before the
command reaches the remote machine) with `--upgrade`, which forces all
transitive deps including Pillow to their latest compatible versions.
Also adds a plain-text echo before the install so users see progress
instead of a silent hang during the 2-4 minute install.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The install method for aider, gptme, and open-interpreter was changed
from pip to `uv tool install` across all clouds. The mock test
assertions still checked for the old `pip.*install.*` patterns, causing
9 failures (3 agents × 3 clouds).
Update patterns to match the actual `uv tool install` commands now used
in all cloud scripts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…louds
- Add < /dev/null to ssh_run_server and generic_ssh_wait to prevent SSH
stdin theft causing sequential install/verify/configure steps to hang
- Add ServerAliveInterval, ServerAliveCountMax, ConnectTimeout to default
SSH_OPTS so long-running installs don't silently drop on flaky networks
- Remove 2>/dev/null from Fly.io run_server so remote command errors are
no longer silently swallowed (--quiet flag still suppresses flyctl noise)
- Fix Fly.io printf '%q' double-quoting: remove extra quotes around
$escaped_cmd that prevented the remote shell from consuming escapes,
breaking && || | operators in commands
- Remove broken printf '%q' from Daytona run_server and interactive_session
where it escaped shell operators into literal characters since daytona exec
has no intermediate shell layer
- Pin aider to --python 3.12 instead of --with audioop-lts across all clouds
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts:
#	aws/aider.sh
#	daytona/aider.sh
#	digitalocean/aider.sh
#	fly/aider.sh
#	gcp/aider.sh
#	hetzner/aider.sh
#	local/aider.sh
#	ovh/aider.sh
#	sprite/aider.sh
fly ssh console -C does not allocate a pseudo-terminal by default,
causing interactive TUI agents (aider, claude) to fail with
"Input is not a terminal (fd=0)" or completely unresponsive input.
Adding --pty forces PTY allocation, matching how other clouds handle
interactive sessions (SSH uses -t, Sprite uses -tty).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Review

Verdict: APPROVED
Commit: bc2e480

Summary

This PR fixes SSH hangs and pins aider to Python 3.12. The command escaping changes are architecturally correct for each provider's exec model.

Findings

MEDIUM — fly/lib/common.sh:384,389,422 — Removed double quotes around escaped commands

  • Technically creates word-splitting risk, but all callers pass hardcoded trusted strings
  • No user input flows into run_server() or interactive_session()
  • Risk mitigated by hardcoded command construction pattern throughout codebase

LOW — daytona/lib/common.sh:218,248 — Removed printf '%q' escaping

  • Architecturally correct for Daytona's exec ... bash -c model
  • Shell operators must remain unescaped to function properly
  • All callers verified to pass trusted hardcoded commands only

POSITIVE — shared/common.sh:2169,2221 — Added < /dev/null to SSH calls

  • Prevents SSH from consuming parent script's stdin
  • Fixes hangs when sequential SSH calls steal input from later prompts
  • Good defensive practice

Tests

  • bash -n: PASS (all modified .sh files)
  • test suite: PASS (80/80 tests passed)
  • curl|bash compatibility: OK (source fallback pattern unchanged)
  • macOS bash 3.x compat: OK (no bash 4+ features introduced)

Recommendation

The removal of command escaping is safe under the current architecture where all commands are hardcoded. Future code must maintain this invariant — any user input MUST go through validation (model IDs, paths) before command construction.

The PR includes excellent inline documentation explaining WHY escaping was removed, which will help prevent future regressions.


-- security/pr-reviewer

@louisgv
louisgv merged commit db4aaa0 into OpenRouterLabs:mainFeb 18, 2026
3 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@AhmedTMM@louisgv
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: prevent SSH hangs, fix command escaping, pin Python 3.12 for aider - #1439

Merged
louisgv merged 6 commits into
OpenRouterLabs:mainfrom
AhmedTMM:fix/aider-pipx-install
Feb 18, 2026
Merged

fix: prevent SSH hangs, fix command escaping, pin Python 3.12 for aider#1439
louisgv merged 6 commits into
OpenRouterLabs:mainfrom
AhmedTMM:fix/aider-pipx-install

Conversation

@AhmedTMM

Copy link
Copy Markdown
Collaborator

Summary

  • Fix SSH stdin theft — add < /dev/null to ssh_run_server and generic_ssh_wait so sequential SSH calls don't steal stdin from the parent script, which was the Refactor spawn scripts with shared library and OAuth fallback #1 cause of agent install hangs on SSH-based clouds (Hetzner, AWS, DigitalOcean, GCP, OVH)
  • Add SSH keepalive — add ServerAliveInterval=15, ServerAliveCountMax=3, ConnectTimeout=10 to default SSH_OPTS to prevent silent connection drops during long-running installs
  • Restore Fly.io stderr — remove 2>/dev/null from Fly.io run_server so remote command errors are visible instead of silently swallowed
  • Fix Fly.io command escaping — remove extra double-quotes around $escaped_cmd in run_server and interactive_session that broke &&, ||, | operators
  • Fix Daytona command escaping — remove broken printf '%q' from run_server and interactive_session where it escaped shell operators into literal characters (no intermediate shell layer to consume the escapes)
  • Pin aider to Python 3.12 — replace --with audioop-lts with --python 3.12 across all 9 clouds, letting uv use Python 3.12 which still has the audioop module built in

Test plan

  • bash -n syntax check on all 12 modified files
  • bash test/mock.sh — 270 tests, same pass rate as baseline (pre-existing flaky timeouts unrelated to changes)
  • Manual test: spawn aider fly — verify aider installs and launches without hanging
  • Manual test: spawn claude hetzner — verify SSH session doesn't hang during install
  • Verify Daytona multi-part commands (&& chains) work correctly after printf '%q' removal

🤖 Generated with Claude Code

AhmedTMMand others added 6 commits February 18, 2026 00:00
…all clouds
aider-chat on Python 3.13 fails with `ImportError: cannot import name
'_imaging' from 'PIL'` when an old Pillow version (pre-10.4) is resolved
— those releases have no Python 3.13 binary wheels, so the C extension
is missing at runtime.
Replace `--with 'Pillow>=10.2.0'` (which was silently broken — the `>`
and single quotes get mangled by `printf '%q'` in run_server before the
command reaches the remote machine) with `--upgrade`, which forces all
transitive deps including Pillow to their latest compatible versions.
Also adds a plain-text echo before the install so users see progress
instead of a silent hang during the 2-4 minute install.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The install method for aider, gptme, and open-interpreter was changed
from pip to `uv tool install` across all clouds. The mock test
assertions still checked for the old `pip.*install.*` patterns, causing
9 failures (3 agents × 3 clouds).
Update patterns to match the actual `uv tool install` commands now used
in all cloud scripts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…louds
- Add < /dev/null to ssh_run_server and generic_ssh_wait to prevent SSH
stdin theft causing sequential install/verify/configure steps to hang
- Add ServerAliveInterval, ServerAliveCountMax, ConnectTimeout to default
SSH_OPTS so long-running installs don't silently drop on flaky networks
- Remove 2>/dev/null from Fly.io run_server so remote command errors are
no longer silently swallowed (--quiet flag still suppresses flyctl noise)
- Fix Fly.io printf '%q' double-quoting: remove extra quotes around
$escaped_cmd that prevented the remote shell from consuming escapes,
breaking && || | operators in commands
- Remove broken printf '%q' from Daytona run_server and interactive_session
where it escaped shell operators into literal characters since daytona exec
has no intermediate shell layer
- Pin aider to --python 3.12 instead of --with audioop-lts across all clouds
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts:
#	aws/aider.sh
#	daytona/aider.sh
#	digitalocean/aider.sh
#	fly/aider.sh
#	gcp/aider.sh
#	hetzner/aider.sh
#	local/aider.sh
#	ovh/aider.sh
#	sprite/aider.sh
fly ssh console -C does not allocate a pseudo-terminal by default,
causing interactive TUI agents (aider, claude) to fail with
"Input is not a terminal (fd=0)" or completely unresponsive input.
Adding --pty forces PTY allocation, matching how other clouds handle
interactive sessions (SSH uses -t, Sprite uses -tty).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Review

Verdict: APPROVED
Commit: bc2e480

Summary

This PR fixes SSH hangs and pins aider to Python 3.12. The command escaping changes are architecturally correct for each provider's exec model.

Findings

MEDIUM — fly/lib/common.sh:384,389,422 — Removed double quotes around escaped commands

  • Technically creates word-splitting risk, but all callers pass hardcoded trusted strings
  • No user input flows into run_server() or interactive_session()
  • Risk mitigated by hardcoded command construction pattern throughout codebase

LOW — daytona/lib/common.sh:218,248 — Removed printf '%q' escaping

  • Architecturally correct for Daytona's exec ... bash -c model
  • Shell operators must remain unescaped to function properly
  • All callers verified to pass trusted hardcoded commands only

POSITIVE — shared/common.sh:2169,2221 — Added < /dev/null to SSH calls

  • Prevents SSH from consuming parent script's stdin
  • Fixes hangs when sequential SSH calls steal input from later prompts
  • Good defensive practice

Tests

  • bash -n: PASS (all modified .sh files)
  • test suite: PASS (80/80 tests passed)
  • curl|bash compatibility: OK (source fallback pattern unchanged)
  • macOS bash 3.x compat: OK (no bash 4+ features introduced)

Recommendation

The removal of command escaping is safe under the current architecture where all commands are hardcoded. Future code must maintain this invariant — any user input MUST go through validation (model IDs, paths) before command construction.

The PR includes excellent inline documentation explaining WHY escaping was removed, which will help prevent future regressions.


-- security/pr-reviewer

@louisgv
louisgv merged commit db4aaa0 into OpenRouterLabs:mainFeb 18, 2026
3 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@AhmedTMM@louisgv
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix: prevent SSH hangs, fix command escaping, pin Python 3.12 for aider - #1439

Merged
louisgv merged 6 commits into
OpenRouterLabs:mainfrom
AhmedTMM:fix/aider-pipx-install
Feb 18, 2026
Merged

fix: prevent SSH hangs, fix command escaping, pin Python 3.12 for aider#1439
louisgv merged 6 commits into
OpenRouterLabs:mainfrom
AhmedTMM:fix/aider-pipx-install

Conversation

@AhmedTMM

Copy link
Copy Markdown
Collaborator

Summary

  • Fix SSH stdin theft — add < /dev/null to ssh_run_server and generic_ssh_wait so sequential SSH calls don't steal stdin from the parent script, which was the Refactor spawn scripts with shared library and OAuth fallback #1 cause of agent install hangs on SSH-based clouds (Hetzner, AWS, DigitalOcean, GCP, OVH)
  • Add SSH keepalive — add ServerAliveInterval=15, ServerAliveCountMax=3, ConnectTimeout=10 to default SSH_OPTS to prevent silent connection drops during long-running installs
  • Restore Fly.io stderr — remove 2>/dev/null from Fly.io run_server so remote command errors are visible instead of silently swallowed
  • Fix Fly.io command escaping — remove extra double-quotes around $escaped_cmd in run_server and interactive_session that broke &&, ||, | operators
  • Fix Daytona command escaping — remove broken printf '%q' from run_server and interactive_session where it escaped shell operators into literal characters (no intermediate shell layer to consume the escapes)
  • Pin aider to Python 3.12 — replace --with audioop-lts with --python 3.12 across all 9 clouds, letting uv use Python 3.12 which still has the audioop module built in

Test plan

  • bash -n syntax check on all 12 modified files
  • bash test/mock.sh — 270 tests, same pass rate as baseline (pre-existing flaky timeouts unrelated to changes)
  • Manual test: spawn aider fly — verify aider installs and launches without hanging
  • Manual test: spawn claude hetzner — verify SSH session doesn't hang during install
  • Verify Daytona multi-part commands (&& chains) work correctly after printf '%q' removal

🤖 Generated with Claude Code

AhmedTMMand others added 6 commits February 18, 2026 00:00
…all clouds
aider-chat on Python 3.13 fails with `ImportError: cannot import name
'_imaging' from 'PIL'` when an old Pillow version (pre-10.4) is resolved
— those releases have no Python 3.13 binary wheels, so the C extension
is missing at runtime.
Replace `--with 'Pillow>=10.2.0'` (which was silently broken — the `>`
and single quotes get mangled by `printf '%q'` in run_server before the
command reaches the remote machine) with `--upgrade`, which forces all
transitive deps including Pillow to their latest compatible versions.
Also adds a plain-text echo before the install so users see progress
instead of a silent hang during the 2-4 minute install.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The install method for aider, gptme, and open-interpreter was changed
from pip to `uv tool install` across all clouds. The mock test
assertions still checked for the old `pip.*install.*` patterns, causing
9 failures (3 agents × 3 clouds).
Update patterns to match the actual `uv tool install` commands now used
in all cloud scripts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…louds
- Add < /dev/null to ssh_run_server and generic_ssh_wait to prevent SSH
stdin theft causing sequential install/verify/configure steps to hang
- Add ServerAliveInterval, ServerAliveCountMax, ConnectTimeout to default
SSH_OPTS so long-running installs don't silently drop on flaky networks
- Remove 2>/dev/null from Fly.io run_server so remote command errors are
no longer silently swallowed (--quiet flag still suppresses flyctl noise)
- Fix Fly.io printf '%q' double-quoting: remove extra quotes around
$escaped_cmd that prevented the remote shell from consuming escapes,
breaking && || | operators in commands
- Remove broken printf '%q' from Daytona run_server and interactive_session
where it escaped shell operators into literal characters since daytona exec
has no intermediate shell layer
- Pin aider to --python 3.12 instead of --with audioop-lts across all clouds
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts:
#	aws/aider.sh
#	daytona/aider.sh
#	digitalocean/aider.sh
#	fly/aider.sh
#	gcp/aider.sh
#	hetzner/aider.sh
#	local/aider.sh
#	ovh/aider.sh
#	sprite/aider.sh
fly ssh console -C does not allocate a pseudo-terminal by default,
causing interactive TUI agents (aider, claude) to fail with
"Input is not a terminal (fd=0)" or completely unresponsive input.
Adding --pty forces PTY allocation, matching how other clouds handle
interactive sessions (SSH uses -t, Sprite uses -tty).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Review

Verdict: APPROVED
Commit: bc2e480

Summary

This PR fixes SSH hangs and pins aider to Python 3.12. The command escaping changes are architecturally correct for each provider's exec model.

Findings

MEDIUM — fly/lib/common.sh:384,389,422 — Removed double quotes around escaped commands

  • Technically creates word-splitting risk, but all callers pass hardcoded trusted strings
  • No user input flows into run_server() or interactive_session()
  • Risk mitigated by hardcoded command construction pattern throughout codebase

LOW — daytona/lib/common.sh:218,248 — Removed printf '%q' escaping

  • Architecturally correct for Daytona's exec ... bash -c model
  • Shell operators must remain unescaped to function properly
  • All callers verified to pass trusted hardcoded commands only

POSITIVE — shared/common.sh:2169,2221 — Added < /dev/null to SSH calls

  • Prevents SSH from consuming parent script's stdin
  • Fixes hangs when sequential SSH calls steal input from later prompts
  • Good defensive practice

Tests

  • bash -n: PASS (all modified .sh files)
  • test suite: PASS (80/80 tests passed)
  • curl|bash compatibility: OK (source fallback pattern unchanged)
  • macOS bash 3.x compat: OK (no bash 4+ features introduced)

Recommendation

The removal of command escaping is safe under the current architecture where all commands are hardcoded. Future code must maintain this invariant — any user input MUST go through validation (model IDs, paths) before command construction.

The PR includes excellent inline documentation explaining WHY escaping was removed, which will help prevent future regressions.


-- security/pr-reviewer

@louisgv
louisgv merged commit db4aaa0 into OpenRouterLabs:mainFeb 18, 2026
3 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@AhmedTMM@louisgv
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: prevent SSH hangs, fix command escaping, pin Python 3.12 for aider - #1439

Merged
louisgv merged 6 commits into
OpenRouterLabs:mainfrom
AhmedTMM:fix/aider-pipx-install
Feb 18, 2026
Merged

fix: prevent SSH hangs, fix command escaping, pin Python 3.12 for aider#1439
louisgv merged 6 commits into
OpenRouterLabs:mainfrom
AhmedTMM:fix/aider-pipx-install

Conversation

@AhmedTMM

Copy link
Copy Markdown
Collaborator

Summary

  • Fix SSH stdin theft — add < /dev/null to ssh_run_server and generic_ssh_wait so sequential SSH calls don't steal stdin from the parent script, which was the Refactor spawn scripts with shared library and OAuth fallback #1 cause of agent install hangs on SSH-based clouds (Hetzner, AWS, DigitalOcean, GCP, OVH)
  • Add SSH keepalive — add ServerAliveInterval=15, ServerAliveCountMax=3, ConnectTimeout=10 to default SSH_OPTS to prevent silent connection drops during long-running installs
  • Restore Fly.io stderr — remove 2>/dev/null from Fly.io run_server so remote command errors are visible instead of silently swallowed
  • Fix Fly.io command escaping — remove extra double-quotes around $escaped_cmd in run_server and interactive_session that broke &&, ||, | operators
  • Fix Daytona command escaping — remove broken printf '%q' from run_server and interactive_session where it escaped shell operators into literal characters (no intermediate shell layer to consume the escapes)
  • Pin aider to Python 3.12 — replace --with audioop-lts with --python 3.12 across all 9 clouds, letting uv use Python 3.12 which still has the audioop module built in

Test plan

  • bash -n syntax check on all 12 modified files
  • bash test/mock.sh — 270 tests, same pass rate as baseline (pre-existing flaky timeouts unrelated to changes)
  • Manual test: spawn aider fly — verify aider installs and launches without hanging
  • Manual test: spawn claude hetzner — verify SSH session doesn't hang during install
  • Verify Daytona multi-part commands (&& chains) work correctly after printf '%q' removal

🤖 Generated with Claude Code

AhmedTMMand others added 6 commits February 18, 2026 00:00
…all clouds
aider-chat on Python 3.13 fails with `ImportError: cannot import name
'_imaging' from 'PIL'` when an old Pillow version (pre-10.4) is resolved
— those releases have no Python 3.13 binary wheels, so the C extension
is missing at runtime.
Replace `--with 'Pillow>=10.2.0'` (which was silently broken — the `>`
and single quotes get mangled by `printf '%q'` in run_server before the
command reaches the remote machine) with `--upgrade`, which forces all
transitive deps including Pillow to their latest compatible versions.
Also adds a plain-text echo before the install so users see progress
instead of a silent hang during the 2-4 minute install.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The install method for aider, gptme, and open-interpreter was changed
from pip to `uv tool install` across all clouds. The mock test
assertions still checked for the old `pip.*install.*` patterns, causing
9 failures (3 agents × 3 clouds).
Update patterns to match the actual `uv tool install` commands now used
in all cloud scripts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…louds
- Add < /dev/null to ssh_run_server and generic_ssh_wait to prevent SSH
stdin theft causing sequential install/verify/configure steps to hang
- Add ServerAliveInterval, ServerAliveCountMax, ConnectTimeout to default
SSH_OPTS so long-running installs don't silently drop on flaky networks
- Remove 2>/dev/null from Fly.io run_server so remote command errors are
no longer silently swallowed (--quiet flag still suppresses flyctl noise)
- Fix Fly.io printf '%q' double-quoting: remove extra quotes around
$escaped_cmd that prevented the remote shell from consuming escapes,
breaking && || | operators in commands
- Remove broken printf '%q' from Daytona run_server and interactive_session
where it escaped shell operators into literal characters since daytona exec
has no intermediate shell layer
- Pin aider to --python 3.12 instead of --with audioop-lts across all clouds
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts:
#	aws/aider.sh
#	daytona/aider.sh
#	digitalocean/aider.sh
#	fly/aider.sh
#	gcp/aider.sh
#	hetzner/aider.sh
#	local/aider.sh
#	ovh/aider.sh
#	sprite/aider.sh
fly ssh console -C does not allocate a pseudo-terminal by default,
causing interactive TUI agents (aider, claude) to fail with
"Input is not a terminal (fd=0)" or completely unresponsive input.
Adding --pty forces PTY allocation, matching how other clouds handle
interactive sessions (SSH uses -t, Sprite uses -tty).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Review

Verdict: APPROVED
Commit: bc2e480

Summary

This PR fixes SSH hangs and pins aider to Python 3.12. The command escaping changes are architecturally correct for each provider's exec model.

Findings

MEDIUM — fly/lib/common.sh:384,389,422 — Removed double quotes around escaped commands

  • Technically creates word-splitting risk, but all callers pass hardcoded trusted strings
  • No user input flows into run_server() or interactive_session()
  • Risk mitigated by hardcoded command construction pattern throughout codebase

LOW — daytona/lib/common.sh:218,248 — Removed printf '%q' escaping

  • Architecturally correct for Daytona's exec ... bash -c model
  • Shell operators must remain unescaped to function properly
  • All callers verified to pass trusted hardcoded commands only

POSITIVE — shared/common.sh:2169,2221 — Added < /dev/null to SSH calls

  • Prevents SSH from consuming parent script's stdin
  • Fixes hangs when sequential SSH calls steal input from later prompts
  • Good defensive practice

Tests

  • bash -n: PASS (all modified .sh files)
  • test suite: PASS (80/80 tests passed)
  • curl|bash compatibility: OK (source fallback pattern unchanged)
  • macOS bash 3.x compat: OK (no bash 4+ features introduced)

Recommendation

The removal of command escaping is safe under the current architecture where all commands are hardcoded. Future code must maintain this invariant — any user input MUST go through validation (model IDs, paths) before command construction.

The PR includes excellent inline documentation explaining WHY escaping was removed, which will help prevent future regressions.


-- security/pr-reviewer

@louisgv
louisgv merged commit db4aaa0 into OpenRouterLabs:mainFeb 18, 2026
3 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@AhmedTMM@louisgv
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: prevent SSH hangs, fix command escaping, pin Python 3.12 for aider - #1439

Merged
louisgv merged 6 commits into
OpenRouterLabs:mainfrom
AhmedTMM:fix/aider-pipx-install
Feb 18, 2026
Merged

fix: prevent SSH hangs, fix command escaping, pin Python 3.12 for aider#1439
louisgv merged 6 commits into
OpenRouterLabs:mainfrom
AhmedTMM:fix/aider-pipx-install

Conversation

@AhmedTMM

Copy link
Copy Markdown
Collaborator

Summary

  • Fix SSH stdin theft — add < /dev/null to ssh_run_server and generic_ssh_wait so sequential SSH calls don't steal stdin from the parent script, which was the Refactor spawn scripts with shared library and OAuth fallback #1 cause of agent install hangs on SSH-based clouds (Hetzner, AWS, DigitalOcean, GCP, OVH)
  • Add SSH keepalive — add ServerAliveInterval=15, ServerAliveCountMax=3, ConnectTimeout=10 to default SSH_OPTS to prevent silent connection drops during long-running installs
  • Restore Fly.io stderr — remove 2>/dev/null from Fly.io run_server so remote command errors are visible instead of silently swallowed
  • Fix Fly.io command escaping — remove extra double-quotes around $escaped_cmd in run_server and interactive_session that broke &&, ||, | operators
  • Fix Daytona command escaping — remove broken printf '%q' from run_server and interactive_session where it escaped shell operators into literal characters (no intermediate shell layer to consume the escapes)
  • Pin aider to Python 3.12 — replace --with audioop-lts with --python 3.12 across all 9 clouds, letting uv use Python 3.12 which still has the audioop module built in

Test plan

  • bash -n syntax check on all 12 modified files
  • bash test/mock.sh — 270 tests, same pass rate as baseline (pre-existing flaky timeouts unrelated to changes)
  • Manual test: spawn aider fly — verify aider installs and launches without hanging
  • Manual test: spawn claude hetzner — verify SSH session doesn't hang during install
  • Verify Daytona multi-part commands (&& chains) work correctly after printf '%q' removal

🤖 Generated with Claude Code

AhmedTMMand others added 6 commits February 18, 2026 00:00
…all clouds
aider-chat on Python 3.13 fails with `ImportError: cannot import name
'_imaging' from 'PIL'` when an old Pillow version (pre-10.4) is resolved
— those releases have no Python 3.13 binary wheels, so the C extension
is missing at runtime.
Replace `--with 'Pillow>=10.2.0'` (which was silently broken — the `>`
and single quotes get mangled by `printf '%q'` in run_server before the
command reaches the remote machine) with `--upgrade`, which forces all
transitive deps including Pillow to their latest compatible versions.
Also adds a plain-text echo before the install so users see progress
instead of a silent hang during the 2-4 minute install.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The install method for aider, gptme, and open-interpreter was changed
from pip to `uv tool install` across all clouds. The mock test
assertions still checked for the old `pip.*install.*` patterns, causing
9 failures (3 agents × 3 clouds).
Update patterns to match the actual `uv tool install` commands now used
in all cloud scripts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…louds
- Add < /dev/null to ssh_run_server and generic_ssh_wait to prevent SSH
stdin theft causing sequential install/verify/configure steps to hang
- Add ServerAliveInterval, ServerAliveCountMax, ConnectTimeout to default
SSH_OPTS so long-running installs don't silently drop on flaky networks
- Remove 2>/dev/null from Fly.io run_server so remote command errors are
no longer silently swallowed (--quiet flag still suppresses flyctl noise)
- Fix Fly.io printf '%q' double-quoting: remove extra quotes around
$escaped_cmd that prevented the remote shell from consuming escapes,
breaking && || | operators in commands
- Remove broken printf '%q' from Daytona run_server and interactive_session
where it escaped shell operators into literal characters since daytona exec
has no intermediate shell layer
- Pin aider to --python 3.12 instead of --with audioop-lts across all clouds
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts:
#	aws/aider.sh
#	daytona/aider.sh
#	digitalocean/aider.sh
#	fly/aider.sh
#	gcp/aider.sh
#	hetzner/aider.sh
#	local/aider.sh
#	ovh/aider.sh
#	sprite/aider.sh
fly ssh console -C does not allocate a pseudo-terminal by default,
causing interactive TUI agents (aider, claude) to fail with
"Input is not a terminal (fd=0)" or completely unresponsive input.
Adding --pty forces PTY allocation, matching how other clouds handle
interactive sessions (SSH uses -t, Sprite uses -tty).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Review

Verdict: APPROVED
Commit: bc2e480

Summary

This PR fixes SSH hangs and pins aider to Python 3.12. The command escaping changes are architecturally correct for each provider's exec model.

Findings

MEDIUM — fly/lib/common.sh:384,389,422 — Removed double quotes around escaped commands

  • Technically creates word-splitting risk, but all callers pass hardcoded trusted strings
  • No user input flows into run_server() or interactive_session()
  • Risk mitigated by hardcoded command construction pattern throughout codebase

LOW — daytona/lib/common.sh:218,248 — Removed printf '%q' escaping

  • Architecturally correct for Daytona's exec ... bash -c model
  • Shell operators must remain unescaped to function properly
  • All callers verified to pass trusted hardcoded commands only

POSITIVE — shared/common.sh:2169,2221 — Added < /dev/null to SSH calls

  • Prevents SSH from consuming parent script's stdin
  • Fixes hangs when sequential SSH calls steal input from later prompts
  • Good defensive practice

Tests

  • bash -n: PASS (all modified .sh files)
  • test suite: PASS (80/80 tests passed)
  • curl|bash compatibility: OK (source fallback pattern unchanged)
  • macOS bash 3.x compat: OK (no bash 4+ features introduced)

Recommendation

The removal of command escaping is safe under the current architecture where all commands are hardcoded. Future code must maintain this invariant — any user input MUST go through validation (model IDs, paths) before command construction.

The PR includes excellent inline documentation explaining WHY escaping was removed, which will help prevent future regressions.


-- security/pr-reviewer

@louisgv
louisgv merged commit db4aaa0 into OpenRouterLabs:mainFeb 18, 2026
3 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@AhmedTMM@louisgv
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix: prevent SSH hangs, fix command escaping, pin Python 3.12 for aider - #1439

Merged
louisgv merged 6 commits into
OpenRouterLabs:mainfrom
AhmedTMM:fix/aider-pipx-install
Feb 18, 2026
Merged

fix: prevent SSH hangs, fix command escaping, pin Python 3.12 for aider#1439
louisgv merged 6 commits into
OpenRouterLabs:mainfrom
AhmedTMM:fix/aider-pipx-install

Conversation

@AhmedTMM

Copy link
Copy Markdown
Collaborator

Summary

  • Fix SSH stdin theft — add < /dev/null to ssh_run_server and generic_ssh_wait so sequential SSH calls don't steal stdin from the parent script, which was the Refactor spawn scripts with shared library and OAuth fallback #1 cause of agent install hangs on SSH-based clouds (Hetzner, AWS, DigitalOcean, GCP, OVH)
  • Add SSH keepalive — add ServerAliveInterval=15, ServerAliveCountMax=3, ConnectTimeout=10 to default SSH_OPTS to prevent silent connection drops during long-running installs
  • Restore Fly.io stderr — remove 2>/dev/null from Fly.io run_server so remote command errors are visible instead of silently swallowed
  • Fix Fly.io command escaping — remove extra double-quotes around $escaped_cmd in run_server and interactive_session that broke &&, ||, | operators
  • Fix Daytona command escaping — remove broken printf '%q' from run_server and interactive_session where it escaped shell operators into literal characters (no intermediate shell layer to consume the escapes)
  • Pin aider to Python 3.12 — replace --with audioop-lts with --python 3.12 across all 9 clouds, letting uv use Python 3.12 which still has the audioop module built in

Test plan

  • bash -n syntax check on all 12 modified files
  • bash test/mock.sh — 270 tests, same pass rate as baseline (pre-existing flaky timeouts unrelated to changes)
  • Manual test: spawn aider fly — verify aider installs and launches without hanging
  • Manual test: spawn claude hetzner — verify SSH session doesn't hang during install
  • Verify Daytona multi-part commands (&& chains) work correctly after printf '%q' removal

🤖 Generated with Claude Code

AhmedTMMand others added 6 commits February 18, 2026 00:00
…all clouds
aider-chat on Python 3.13 fails with `ImportError: cannot import name
'_imaging' from 'PIL'` when an old Pillow version (pre-10.4) is resolved
— those releases have no Python 3.13 binary wheels, so the C extension
is missing at runtime.
Replace `--with 'Pillow>=10.2.0'` (which was silently broken — the `>`
and single quotes get mangled by `printf '%q'` in run_server before the
command reaches the remote machine) with `--upgrade`, which forces all
transitive deps including Pillow to their latest compatible versions.
Also adds a plain-text echo before the install so users see progress
instead of a silent hang during the 2-4 minute install.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The install method for aider, gptme, and open-interpreter was changed
from pip to `uv tool install` across all clouds. The mock test
assertions still checked for the old `pip.*install.*` patterns, causing
9 failures (3 agents × 3 clouds).
Update patterns to match the actual `uv tool install` commands now used
in all cloud scripts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…louds
- Add < /dev/null to ssh_run_server and generic_ssh_wait to prevent SSH
stdin theft causing sequential install/verify/configure steps to hang
- Add ServerAliveInterval, ServerAliveCountMax, ConnectTimeout to default
SSH_OPTS so long-running installs don't silently drop on flaky networks
- Remove 2>/dev/null from Fly.io run_server so remote command errors are
no longer silently swallowed (--quiet flag still suppresses flyctl noise)
- Fix Fly.io printf '%q' double-quoting: remove extra quotes around
$escaped_cmd that prevented the remote shell from consuming escapes,
breaking && || | operators in commands
- Remove broken printf '%q' from Daytona run_server and interactive_session
where it escaped shell operators into literal characters since daytona exec
has no intermediate shell layer
- Pin aider to --python 3.12 instead of --with audioop-lts across all clouds
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts:
#	aws/aider.sh
#	daytona/aider.sh
#	digitalocean/aider.sh
#	fly/aider.sh
#	gcp/aider.sh
#	hetzner/aider.sh
#	local/aider.sh
#	ovh/aider.sh
#	sprite/aider.sh
fly ssh console -C does not allocate a pseudo-terminal by default,
causing interactive TUI agents (aider, claude) to fail with
"Input is not a terminal (fd=0)" or completely unresponsive input.
Adding --pty forces PTY allocation, matching how other clouds handle
interactive sessions (SSH uses -t, Sprite uses -tty).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@louisgvlouisgv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Review

Verdict: APPROVED
Commit: bc2e480

Summary

This PR fixes SSH hangs and pins aider to Python 3.12. The command escaping changes are architecturally correct for each provider's exec model.

Findings

MEDIUM — fly/lib/common.sh:384,389,422 — Removed double quotes around escaped commands

  • Technically creates word-splitting risk, but all callers pass hardcoded trusted strings
  • No user input flows into run_server() or interactive_session()
  • Risk mitigated by hardcoded command construction pattern throughout codebase

LOW — daytona/lib/common.sh:218,248 — Removed printf '%q' escaping

  • Architecturally correct for Daytona's exec ... bash -c model
  • Shell operators must remain unescaped to function properly
  • All callers verified to pass trusted hardcoded commands only

POSITIVE — shared/common.sh:2169,2221 — Added < /dev/null to SSH calls

  • Prevents SSH from consuming parent script's stdin
  • Fixes hangs when sequential SSH calls steal input from later prompts
  • Good defensive practice

Tests

  • bash -n: PASS (all modified .sh files)
  • test suite: PASS (80/80 tests passed)
  • curl|bash compatibility: OK (source fallback pattern unchanged)
  • macOS bash 3.x compat: OK (no bash 4+ features introduced)

Recommendation

The removal of command escaping is safe under the current architecture where all commands are hardcoded. Future code must maintain this invariant — any user input MUST go through validation (model IDs, paths) before command construction.

The PR includes excellent inline documentation explaining WHY escaping was removed, which will help prevent future regressions.


-- security/pr-reviewer

@louisgv
louisgv merged commit db4aaa0 into OpenRouterLabs:mainFeb 18, 2026
3 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@AhmedTMM@louisgv