Skip to content

Remote launcher writes $ instead of its pid into the runtime install lock #15696

Closed

Description

@altaywtf

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/desktop

Steps to reproduce

  1. Connect the desktop app to an SSH remote that does not have the app's runtime version installed yet.
  2. On the remote, read the generated launcher ~/.t3/ssh-launch/<id>/run-t3.sh. Its lock line is:
    printf '%s\n' "$" > "$T3_LOCK/pid.tmp" && mv "$T3_LOCK/pid.tmp" "$T3_LOCK/pid"
  3. Start a second launch while the first is still downloading the archive (a reconnect does this on a slow link).

The cause in one line:

"A @@X@@ B".replaceAll("@@X@@", 'printf "$$"') // 'A printf "$" B'

Expected behavior

The install lock records the installing launcher's pid, so a second launch waits for it, as the comments around the lock describe.

Actual behavior

  • The lock's pid file holds a literal $. kill -0 '$' fails, so every waiting launch treats the owner as dead, deletes the lock and starts its own download into a second staging directory.
  • applyScriptPlaceholders inserts values with replaceAll(token, value). A string replacement treats $$ as an escaped $, so the runner's "$$" becomes "$" when the runner is inserted into the launch and pairing scripts.
  • tunnel.test.ts checks buildRemoteT3RunnerScript output before that insertion, so it passes.
  • Suggested fix: result.replaceAll(`@@${token}@@`, () => value), plus a test that the launch script contains "$$" > "$T3_LOCK/pid.tmp". A function replacer also keeps $&, $` and $' in any inserted script literal.

Impact

Minor bug or occasional failure

Version or commit

v0.0.45 (observed on the remote); same code on main at 4ee6bfd

Environment

Desktop app on macOS, SSH remote on Linux arm64


Written by an agent (Claude Code, Claude Opus 5.5)

Activity

  1. juliusmarminge commented on Oct 4, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Triage

    Thanks for the precise report, @altaywtf. The one-line replaceAll repro made this quick to confirm.

    What I found

    Confirmed on main at 4ee6bfd.

    • applyScriptPlaceholders in packages/ssh/src/tunnel.ts inserts values with String.prototype.replaceAll. In a string replacement, $$ becomes $, so the runner's printf '%s\n' "$$" > "$T3_LOCK/pid.tmp" line is written into ~/.t3/ssh-launch/<id>/run-t3.sh as "$". The launch and pairing scripts both embed the runner through @@T3_RUNNER_SCRIPT@@.
    • The launch script's own run-t3.next.$$ is unaffected, because that $$ is in the template rather than in a replacement value.
    • kill -0 '$' always fails, so a second launch during a slow download treats the lock owner as dead and starts another install. A single launch still installs fine.
    • tunnel.test.ts only checks the runner before it's inserted, so the existing test passes.

    Likely fix area

    There's already an open PR for this, #14598 by @Gigioxx. It switches applyScriptPlaceholders to a function replacer and adds a test that the launch script embeds the runner unchanged. It's mergeable and its checks are green, so it matches what you suggested.

    A maintainer will decide on the fix direction.

  2. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 4, 2026
  3. juliusmarminge commented on Oct 11, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Fixed by #14598, which just landed on main: applyScriptPlaceholders now uses a replacer function, so the runner's "$$" reaches the launch script intact and the install lock records the real PID. It also adds a byte-for-byte test that the launch script embeds the runner. Closing — thanks for the precise report!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions