Skip to content

Daemon self-update fails with exit 127 when node comes from an older mise install: the npm wrapper's mise reshim is not on the service PATH #6079

Closed

Description

@alexandre-vl

Summary

After the server moved to a nightly with a newer daemon protocol (237 → 246), a Mac machine stayed disconnected. Its auto-updating LaunchAgent daemon downloaded and installed the server's bb-app, but the install command exited 127, so bb recorded a failed update, never restarted into the new build, and kept retrying with backoff. The npm beside a node installed by an older mise is a bash wrapper that runs mise reshim after every global install, and mise is not on the LaunchAgent's PATH. Expected: the daemon restarts on the server's protocol, as it does with a plain node install.

Versions and environment

  • Server: bb-app 0.46.2-nightly.39620384282.1, npm install run by a systemd user unit (server moved here with bb server move), Debian 13, Node 24.17.0 from mise.
  • Affected machine: macOS (version not recorded), bb-app 0.46.1 daemon run by the LaunchAgent app.getbb.host-daemon.<slug> that the desktop app installed with install-machine.sh --adopt after the server move (--auto-update), Node 24.18.0 from mise (~/.local/share/mise/installs/node/24.18.0/bin/node in ProgramArguments), bb Connect.
  • Repro machine: Debian 13 (trixie), mise 2026.9.13 with its binary in ~/.local/bin, bb main at 2799d45.

Steps to reproduce

Smallest repro: a global install through the npm wrapper of a node that an older mise installed, with PATH built the way defaultInstallTarball builds it (node's directory, then launchd's default PATH):

NODE_DIR=~/.local/share/mise/installs/node/24.17.0/bin
mkdir /tmp/mise-npm-prefix && cd /tmp
env -i HOME="$HOME" PATH="$NODE_DIR:/usr/bin:/bin:/usr/sbin:/sbin" \
  "$NODE_DIR/npm" install -g --prefix /tmp/mise-npm-prefix bb-app@0.46.1
echo "exit=$?"

Through bb's own code path, from a bb checkout at 2799d45 with dependencies installed:

pnpm exec turbo run generate:profile-worker --filter=@bb/process-utils
mkdir /tmp/bb-repro /tmp/bb-repro-dl /tmp/bb-repro-prefix-a /tmp/bb-repro-prefix-b
cat > /tmp/bb-repro/repro-self-update.mts <<EOF
import { defaultInstallTarball, defaultRunProcess } from "$PWD/apps/host-daemon/src/protocol-self-update.ts";

const tarball = process.argv[2];
if (tarball === undefined) throw new Error("usage: repro-self-update.mts <tarball>");
console.log("PATH:", process.env.PATH);
console.log("execPath:", process.execPath);
try {
  await defaultInstallTarball(tarball, defaultRunProcess);
  console.log("defaultInstallTarball: resolved");
} catch (error) {
  const e = error as { code?: unknown; stdout?: string; stderr?: string };
  console.log("defaultInstallTarball: rejected, code", e.code);
  console.log("stdout:", JSON.stringify(e.stdout));
  console.log("stderr:", JSON.stringify(e.stderr));
}
EOF
curl -fsS -o /tmp/bb-repro-dl/bb-app.tgz http://127.0.0.1:38886/install/bb-app.tgz
NODE=~/.local/share/mise/installs/node/24.17.0/bin/node
env -i HOME="$HOME" PATH=/usr/bin:/bin:/usr/sbin:/sbin BB_APP_NPM_PREFIX=/tmp/bb-repro-prefix-a \
  "$NODE" --import tsx /tmp/bb-repro/repro-self-update.mts /tmp/bb-repro-dl/bb-app.tgz
env -i HOME="$HOME" PATH="$HOME/.local/bin:/usr/bin:/bin:/usr/sbin:/sbin" BB_APP_NPM_PREFIX=/tmp/bb-repro-prefix-b \
  "$NODE" --import tsx /tmp/bb-repro/repro-self-update.mts /tmp/bb-repro-dl/bb-app.tgz

Does not reproduce:

  • with mise's directory on PATH (second run above);
  • with the wrapper mise wrote for node 22.19.0 on 2026-10-21, which runs mise reshim || true (line 101, "Run mise reshim, ignore its exit code"): the same smallest repro prints mise: command not found and exits 0. The wrapper written for 24.17.0 on 2026-06-21 runs a bare mise reshim (line 76) under set -euo pipefail.

Expected vs actual

Smallest repro, 24.17.0 wrapper (the package is installed; lib/node_modules/bb-app/package.json reports 0.46.1):

added 52 packages in 2s

24 packages are looking for funding
  run `npm fund` for details
Reshimming mise 24.17.0...
/home/<user>/.local/share/mise/installs/node/24.17.0/bin/npm: line 76: mise: command not found
exit=127

bb code path, launchd PATH:

PATH: /usr/bin:/bin:/usr/sbin:/sbin
execPath: /home/<user>/.local/share/mise/installs/node/24.17.0/bin/node
defaultInstallTarball: rejected, code 127
stdout: "\nadded 46 packages in 2s\n\n8 packages are looking for funding\n  run `npm fund` for details\n"
stderr: "npm warn Unknown cli config \"--allow-scripts\". This will stop working in the next major version of npm.\nReshimming mise 24.17.0...\n/home/<user>/.local/share/mise/installs/node/24.17.0/bin/npm: line 76: mise: command not found\n"

bb code path, mise on PATH:

PATH: /home/<user>/.local/bin:/usr/bin:/bin:/usr/sbin:/sbin
execPath: /home/<user>/.local/share/mise/installs/node/24.17.0/bin/node
defaultInstallTarball: resolved

Affected Mac, ~/.bb/logs/host-daemon*.log (stack trimmed):

{"level":50,"time":1792615491650,"component":"host-daemon","serverUrl":"https://<name>.getbb.app","err":{"type":"Error","message":"Command failed: npm install -g --allow-scripts=better-sqlite3,node-pty,@parcel/watcher --prefix /Users/<user>/.bb/npm /Users/<user>/.bb/bb-app-update-1087.tgz\nReshimming mise 24.18.0...\n/Users/<user>/.local/share/mise/installs/node/24.18.0/bin/npm: line 75: mise: command not found\n",…,"code":127,"killed":false,"signal":null,…,"stdout":"\nchanged 46 packages in 2s\n\n8 packages are looking for funding\n  run `npm fund` for details\n",…},"msg":"Daemon self-update failed; keeping the current daemon running and retrying normally."}
{"level":40,"time":1792615502581,"component":"host-daemon","serverUrl":"https://<name>.getbb.app","attemptCount":6,"retryAt":1792615648964,"retryDelayMs":160000,"serverProtocolVersion":246,"msg":"Daemon self-update is backing off; keeping the current daemon running."}

Expected: defaultInstallTarball resolves once the package is installed, the daemon restarts, and the machine reconnects on protocol 246.

Evidence

  • defaultInstallTarball prepends dirname(process.execPath) to the inherited PATH and runs the npm it finds there, which is the mise wrapper:
    export async function defaultInstallTarball(
    tarballPath: string,
    runProcess: SelfUpdateProcessRunner,
    ): Promise<void> {
    const executableDirectory = dirname(process.execPath);
    const inheritedPath = process.env.PATH;
    const path = inheritedPath
    ? `${executableDirectory}${delimiter}${inheritedPath}`
    : executableDirectory;
    const rawConfiguredPrefix = process.env.BB_APP_NPM_PREFIX?.trim();
    const configuredPrefix =
    rawConfiguredPrefix === "" ? undefined : rawConfiguredPrefix;
    if (configuredPrefix !== undefined && !isAbsolute(configuredPrefix)) {
    throw new Error("BB_APP_NPM_PREFIX must be an absolute path");
    }
    const prefixArgs =
    configuredPrefix === undefined ? [] : ["--prefix", configuredPrefix];
    await runProcess(
    "npm",
    ["install", "-g", BB_APP_ALLOW_SCRIPTS_ARG, ...prefixArgs, tarballPath],
    {
    env: { ...process.env, PATH: path },
    },
    );
    }
  • Only an "updated" result restarts the daemon:
    if (
    error instanceof ServerResponseError &&
    error.code === "protocol_version_mismatch"
    ) {
    this.protocolMismatchObserved = true;
    const result =
    await this.options.protocolSelfUpdater?.handleProtocolMismatch({
    force: error.protocolUpdateRetryRequested,
    });
    if (result === "updated") {
    await this.options.onSelfUpdateInstalled?.();
    }
  • The LaunchAgent and the systemd unit set no PATH, only BB_APP_NPM_PREFIX and BB_DATA_DIR:
    <key>ProgramArguments</key>
    <array>
    <string>$escaped_node_bin</string>
    <string>$escaped_bb_app</string>
    <string>host-daemon</string>
    <string>--auto-update</string>
    <string>--host-daemon-port</string>
    <string>$host_daemon_port</string>
    <string>--server-url</string>
    <string>$escaped_server</string>
    </array>
    <key>EnvironmentVariables</key>
    <dict>
    <key>BB_APP_NPM_PREFIX</key><string>$escaped_bb_app_npm_prefix</string>
    <key>BB_DATA_DIR</key><string>$escaped_data_dir</string>
    </dict>
    and
    ExecStart="$escaped_node_bin" "$escaped_bb_app" host-daemon --auto-update --host-daemon-port "$host_daemon_port" --server-url "$escaped_server"
    Environment="BB_APP_NPM_PREFIX=$escaped_bb_app_npm_prefix"
    Environment="BB_DATA_DIR=$escaped_data_dir"
  • The server's in-app updater is unaffected: its npm install is not global, and the wrapper passes non-global commands straight to npm-cli.js. It can also already run npm-cli.js with process.execPath:
    const npmCommand =
    args.npmCliPath === null
    ? { args: [] as string[], command: "npm" }
    : { args: [args.npmCliPath], command: process.execPath };
    try {
    args.signal?.throwIfAborted();
    args.onStep(`Downloading bb-app ${args.version}`);
    await runCheckedCommand(args.runner, "npm install", {
    args: [
    ...npmCommand.args,
    "install",
    "--no-audit",
    "--no-fund",
    "--omit=dev",
    `bb-app@${args.version}`,
    ],
  • Server side during the outage: Rejecting daemon session: protocol version mismatch. An older auto-update-enabled daemon will install this server's bb-app; … for the Mac host 32 times in 6 minutes, about every 11 s. bb machine retry-update did not change the outcome.
  • Found from bb thread thr_gme2wi3yrb on the server machine.

What you ruled out

Possible fixes: run npm's JS entry with process.execPath instead of the npm on PATH (the in-app updater already has resolveNpmCliPath), or treat a verified installed package as success when only a post-install step failed, or write a PATH into the generated LaunchAgent and systemd unit (that would also help #5807).

Suggested priority and effort (optional)

Low to medium. It affects every auto-updating machine whose node came from an older mise and whose mise binary is not on the service PATH, and on nightly the daemon protocol changes often (237 → 246 between 0.46.1 and this nightly). Workaround: install the server's version by hand and run launchctl kickstart -k, or add EnvironmentVariables:PATH with mise's directory to the plist and reload it. No data is lost, but the machine is unusable until someone intervenes.

Checks

  • I reproduced this on the latest release or on main, or I say above that I could not.
  • I searched open and closed issues for the same problem.
  • If an agent wrote this, the body ends with > AGENT GENERATED and links the thread or report.

AGENT GENERATED — written by Claude Code from bb thread thr_gme2wi3yrb on the server machine.

Activity

  1. added theissue type on Oct 21, 2026
  2. added
    hostHost daemon, process lifecycle, memory, event loop
    confirmed-reproBug reproduced again from a clean trusted checkout; see linked report
    on Oct 21, 2026
  3. bb-slop-cop commented on Oct 21, 2026

    @bb-slop-cop
    Contributor

    🚨 SLOP COP 🚨 · new-issue-autopilot

    • Classification: Bug · Medium priority · Low effort; area host.
    • Reproduction report: REPRODUCED, high root-cause confidence; applied confirmed-repro. Repeated the reproduction in a second clean trusted-main checkout.
    • Root cause: The updater executes npm's shell launcher. A missing post-install helper exits 127 after npm installs the package, so the updater reports failure and skips the restart path.
    • Fix: Ready PR #6081, 191 changed text lines across three host-daemon files. The regression fails before the fix; all 38 relevant tests, typecheck, and build pass afterward.
    • Limits: Real npm with a generated wrapper on Linux, not the original Mac. Two unrelated broader-suite failures also reproduce on unchanged main.

    AGENT GENERATED

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

    confirmed-reproBug reproduced again from a clean trusted checkout; see linked reporthostHost daemon, process lifecycle, memory, event loop

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions