hold bun at the 1.3 series until 1.4 has been watched
bun 1.4.0 was published 2026-08-20 and https://bun.sh/install serves whatever is newest, so machine-setup provisioning a box today would put a two-day-old release under the platform and every pm2 app except officer-pty. The owner asked for a temporary hold on 1.3. The SERIES is pinned, not a patch — BUN_SERIES=1.3, resolved to the newest 1.3.x at install time so bug fixes inside the series still land, and no 1.3.14 to rot into "the version we happened to pick" the way setup_22.x did for Node. The version reaches the installer as its documented first positional argument (a release tag), and install_bun refuses to run with an empty one: taking whatever bun.sh serves is the single outcome the pin exists to prevent, so no bun at all is the better failure. Loud, and officer-setup's pre-flight already stops on a missing bun. Resolved from the npm registry rather than GitHub's releases API, which is the obvious source and the wrong one — 60 unauthenticated requests an hour per IP, and it returned 504 twice while this was written. An empty answer installs nothing, so where it comes from is load-bearing. The section now reads four states instead of two. "Installed" was one answer covering a machine inside the pin, one behind it, and one past it; past it is the case worth catching, since that is what bun upgrade leaves and nothing else reports it. Above and below get different prose and both offer to move; nothing is corrected silently. What a setup script cannot cover: bun upgrade takes no version argument — latest stable or --canary, verified against its own --help — so it always jumps past the pin without saying so. Hence the doctor check, which reads engines.bun rather than repeating the number, because a second hardcoded copy is a second thing to drift. bun ignores engines (verified: an install declaring 1.9.x ran clean under 1.3.14), so the field is the declaration and doctor is the enforcement. Confirmed bun install --dry-run still resolves frozen with the field present and bun.lock unchanged. This host is already on 1.3.14 and inside the pin, so nothing moved here. Also fixes three more stale process counts in the same block. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -113,6 +113,19 @@ imported by their package name (`officerdev`, `hooks`, `state`, `types`, `helper
|
|||||||
building node-pty, or `officer-pty` cannot load its native module, restore the exact pin
|
building node-pty, or `officer-pty` cannot load its native module, restore the exact pin
|
||||||
first. The source build also needs `build-essential` and `python3`.
|
first. The source build also needs `build-essential` and `python3`.
|
||||||
|
|
||||||
|
**Bun itself is held at the 1.3 series, on purpose and temporarily.** 1.4.0 was published
|
||||||
|
2026-08-20; the owner asked on 2026-08-22 to stay on 1.3 for a few months until 1.4 has been
|
||||||
|
watched. Two places carry that number and they move together: `BUN_SERIES` in
|
||||||
|
`scripts/setup/machine-setup/lib/dev.sh`, which resolves the newest `1.3.x` from the npm registry
|
||||||
|
and passes it to the installer as a release tag, and `engines.bun` in `package.json`, which is
|
||||||
|
what `bun run doctor` reads. bun ignores `engines` (verified — an install declaring `1.9.x` ran
|
||||||
|
clean under 1.3.14), so that field is the declaration and doctor is the enforcement.
|
||||||
|
|
||||||
|
**Do not run `bun upgrade` on a machine running Officer.** It takes no version argument — latest
|
||||||
|
stable or `--canary`, nothing else — so it jumps straight past the pin and says nothing. That is
|
||||||
|
the whole reason `doctor` checks the running version at all; `machine-setup.sh --only 'JavaScript
|
||||||
|
runtimes'` offers to pin a drifted machine back down. Lifting the hold is `todo/infra/`.
|
||||||
|
|
||||||
- **Language**: TypeScript, strict. `bunx tsgo` is clean — keep it that way.
|
- **Language**: TypeScript, strict. `bunx tsgo` is clean — keep it that way.
|
||||||
- **Frontend**: React 19, React Router 7, React Query, Tailwind 4, shadcn/ui + custom components
|
- **Frontend**: React 19, React Router 7, React Query, Tailwind 4, shadcn/ui + custom components
|
||||||
- **Backend**: Hono
|
- **Backend**: Hono
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ the owner's OS user and can never be granted. Indirection there really is accide
|
|||||||
|
|
||||||
## Themes
|
## Themes
|
||||||
|
|
||||||
**88 open, 25 closed**, across 29 themes.
|
**89 open, 25 closed**, across 29 themes.
|
||||||
|
|
||||||
| Theme | Open | Closed |
|
| Theme | Open | Closed |
|
||||||
| ------------------------------------------------------------------------------------------ | ---- | ------ |
|
| ------------------------------------------------------------------------------------------ | ---- | ------ |
|
||||||
@@ -34,7 +34,7 @@ the owner's OS user and can never be granted. Indirection there really is accide
|
|||||||
| [File watchers](todo/file-watchers/) | 1 | 0 |
|
| [File watchers](todo/file-watchers/) | 1 | 0 |
|
||||||
| [Fleet — one command across every server](todo/fleet/) | 5 | 2 |
|
| [Fleet — one command across every server](todo/fleet/) | 5 | 2 |
|
||||||
| [Headscale](todo/headscale/) | 0 | 5 |
|
| [Headscale](todo/headscale/) | 0 | 5 |
|
||||||
| [Infra (alpha)](todo/infra/) | 6 | 0 |
|
| [Infra (alpha)](todo/infra/) | 7 | 0 |
|
||||||
| [Known bugs](todo/known-bugs/) | 3 | 2 |
|
| [Known bugs](todo/known-bugs/) | 3 | 2 |
|
||||||
| [Mobile apps](todo/mobile-apps/) | 3 | 0 |
|
| [Mobile apps](todo/mobile-apps/) | 3 | 0 |
|
||||||
| [Multi-user](todo/multi-user/) | 16 | 5 |
|
| [Multi-user](todo/multi-user/) | 16 | 5 |
|
||||||
@@ -63,6 +63,7 @@ the owner's OS user and can never be granted. Indirection there really is accide
|
|||||||
|
|
||||||
## Newest ten
|
## Newest ten
|
||||||
|
|
||||||
|
- `2026-08-22` **'Revisit the bun 1.3 pin once 1.4 has been watched for a while'** — [infra/revisit-the-bun-1-3-pin-once-1-4-has-been](todo/infra/revisit-the-bun-1-3-pin-once-1-4-has-been.md)
|
||||||
- `2026-08-22` **A plugin's permission should be on for every role by default, off only on request** — [multi-user/a-plugin-s-permission-should-be-on-for-every](todo/multi-user/a-plugin-s-permission-should-be-on-for-every.md)
|
- `2026-08-22` **A plugin's permission should be on for every role by default, off only on request** — [multi-user/a-plugin-s-permission-should-be-on-for-every](todo/multi-user/a-plugin-s-permission-should-be-on-for-every.md)
|
||||||
- `2026-08-22` **A user's settings should export and import as a plain file** — [settings/a-user-s-settings-should-export-and-import-as](todo/settings/a-user-s-settings-should-export-and-import-as.md)
|
- `2026-08-22` **A user's settings should export and import as a plain file** — [settings/a-user-s-settings-should-export-and-import-as](todo/settings/a-user-s-settings-should-export-and-import-as.md)
|
||||||
- `2026-08-22` **The same for system settings, owned by the Super Admin** — [settings/the-same-for-system-settings-owned-by-the](todo/settings/the-same-for-system-settings-owned-by-the.md)
|
- `2026-08-22` **The same for system settings, owned by the Super Admin** — [settings/the-same-for-system-settings-owned-by-the](todo/settings/the-same-for-system-settings-owned-by-the.md)
|
||||||
@@ -72,4 +73,3 @@ the owner's OS user and can never be granted. Indirection there really is accide
|
|||||||
- `2026-08-22` **Review officer-secret-store, built overnight and untested by the owner** — [waiting-on-owner/review-the-officer-secret-store-built-last](todo/waiting-on-owner/review-the-officer-secret-store-built-last.md)
|
- `2026-08-22` **Review officer-secret-store, built overnight and untested by the owner** — [waiting-on-owner/review-the-officer-secret-store-built-last](todo/waiting-on-owner/review-the-officer-secret-store-built-last.md)
|
||||||
- `2026-08-21` **No way to see which affordances are reaching your agents, or to turn one off** — [affordances/no-way-to-see-which-affordances-are-reaching](todo/affordances/no-way-to-see-which-affordances-are-reaching.md)
|
- `2026-08-21` **No way to see which affordances are reaching your agents, or to turn one off** — [affordances/no-way-to-see-which-affordances-are-reaching](todo/affordances/no-way-to-see-which-affordances-are-reaching.md)
|
||||||
- `2026-08-21` **Generalise it: deliver a turn to a named session, from anywhere in the backend** — [agent-delivery/generalise-it-deliver-a-turn-to-a-named](todo/agent-delivery/generalise-it-deliver-a-turn-to-a-named.md)
|
- `2026-08-21` **Generalise it: deliver a turn to a named session, from anywhere in the backend** — [agent-delivery/generalise-it-deliver-a-turn-to-a-named](todo/agent-delivery/generalise-it-deliver-a-turn-to-a-named.md)
|
||||||
- `2026-08-21` **The cost of resuming grows with the transcript** — [agent-delivery/the-cost-of-resuming-grows-with-the-transcript](todo/agent-delivery/the-cost-of-resuming-grows-with-the-transcript.md)
|
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
"src/databases/*",
|
"src/databases/*",
|
||||||
"src/workspaces/*"
|
"src/workspaces/*"
|
||||||
],
|
],
|
||||||
|
"engines": {
|
||||||
|
"bun": "1.3.x"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "node -e \"var v = +process.versions.node.split('.')[0]; if (v < 22) { console.error('Node 22 or newer required (got ' + process.versions.node + '). Run: nvm use 22'); process.exit(1); }\"",
|
"preinstall": "node -e \"var v = +process.versions.node.split('.')[0]; if (v < 22) { console.error('Node 22 or newer required (got ' + process.versions.node + '). Run: nvm use 22'); process.exit(1); }\"",
|
||||||
"gen:index": "bun run ./scripts/gen-index.ts",
|
"gen:index": "bun run ./scripts/gen-index.ts",
|
||||||
|
|||||||
@@ -34,6 +34,44 @@ const fail = (what: string, fix: string) => {
|
|||||||
};
|
};
|
||||||
const pass = (what: string) => console.log(`${green('✓')} ${what}`);
|
const pass = (what: string) => console.log(`${green('✓')} ${what}`);
|
||||||
|
|
||||||
|
console.log('\n── Runtime ──\n');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* bun is deliberately held at a series — `BUN_SERIES` in
|
||||||
|
* scripts/setup/machine-setup/lib/dev.sh — while 1.4 is watched for a while.
|
||||||
|
*
|
||||||
|
* The setup script can pin the INSTALL and nothing more. `bun upgrade` takes no version
|
||||||
|
* argument (it offers latest-stable or --canary and nothing else), so it always jumps to the
|
||||||
|
* newest release and walks a pinned machine off the pin without saying so. This is the only
|
||||||
|
* thing that notices, which is why a version check sits in a script otherwise about accounts.
|
||||||
|
*
|
||||||
|
* The wanted series is READ from `engines.bun` rather than repeated here, because a second
|
||||||
|
* hardcoded copy is a second thing to drift. bun does not enforce that field — verified: an
|
||||||
|
* install declaring `engines.bun: "1.9.x"` ran clean under 1.3.14, no warning — so the field is
|
||||||
|
* the declaration and this is the enforcement.
|
||||||
|
*/
|
||||||
|
const pkg = (await Bun.file(new URL('../package.json', import.meta.url)).json()) as {
|
||||||
|
engines?: { bun?: string };
|
||||||
|
};
|
||||||
|
const wantSeries = (pkg.engines?.bun ?? '').replace(/\.x$/, '');
|
||||||
|
const nowSeries = Bun.version.split('.').slice(0, 2).join('.');
|
||||||
|
|
||||||
|
if (!wantSeries) {
|
||||||
|
fail(
|
||||||
|
'package.json declares no engines.bun',
|
||||||
|
'The setup scripts pin a bun series and nothing else records which one. Add it.',
|
||||||
|
);
|
||||||
|
} else if (nowSeries === wantSeries) {
|
||||||
|
pass(`bun ${Bun.version} ${dim(`(inside the pinned ${wantSeries} series)`)}`);
|
||||||
|
} else {
|
||||||
|
fail(
|
||||||
|
`bun ${Bun.version} is outside the pinned ${wantSeries} series`,
|
||||||
|
`Usually a 'bun upgrade', which always takes the newest release. Pin it back with ` +
|
||||||
|
`'curl -fsSL https://bun.sh/install | bash -s bun-v<newest ${wantSeries}.x>', or raise ` +
|
||||||
|
`engines.bun and BUN_SERIES together if ${nowSeries} is now wanted.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
console.log('\n── Accounts ──\n');
|
console.log('\n── Accounts ──\n');
|
||||||
|
|
||||||
const users = await getUsers();
|
const users = await getUsers();
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ nvim_clone_config() {
|
|||||||
# every process here. officer-pty imports node-pty, a native addon with
|
# every process here. officer-pty imports node-pty, a native addon with
|
||||||
# no Linux prebuild — it compiles against the installed Node on every
|
# no Linux prebuild — it compiles against the installed Node on every
|
||||||
# machine. Either one alone makes Node load-bearing.
|
# machine. Either one alone makes Node load-bearing.
|
||||||
# bun the platform itself and nineteen of the twenty pm2 apps.
|
# bun the platform itself, and every pm2 app except officer-pty.
|
||||||
# pm2 the process manager the ecosystem files are written for.
|
# pm2 the process manager the ecosystem files are written for.
|
||||||
#
|
#
|
||||||
# Deno is not. Nothing in the platform imports it — checked across the whole
|
# Deno is not. Nothing in the platform imports it — checked across the whole
|
||||||
@@ -243,6 +243,69 @@ install_node() {
|
|||||||
npm config set prefix /usr/local >/dev/null 2>&1 || true
|
npm config set prefix /usr/local >/dev/null 2>&1 || true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── Held at the 1.3 series, deliberately and temporarily ──
|
||||||
|
#
|
||||||
|
# bun 1.4.0 landed on 2026-08-20 and https://bun.sh/install takes whatever is
|
||||||
|
# newest, so a machine provisioned two days later puts a brand-new release under
|
||||||
|
# the platform and every pm2 app it runs. Held at 1.3 until 1.4 has been watched
|
||||||
|
# for a while. That is the owner's call and it is meant to be revisited in a few
|
||||||
|
# months, not a permanent ceiling — raise BUN_SERIES when 1.4 has earned it.
|
||||||
|
#
|
||||||
|
# The SERIES is pinned, not a patch. 1.3.x fixes still land and should still be
|
||||||
|
# taken; a hardcoded 1.3.14 would rot into "the version we happened to pick"
|
||||||
|
# exactly the way setup_22.x did for Node.
|
||||||
|
#
|
||||||
|
# This covers the INSTALL path only, and cannot cover the other one: `bun upgrade`
|
||||||
|
# takes no version argument — checked against `bun upgrade --help` on 1.3.14, which
|
||||||
|
# offers latest-stable or --canary and nothing else — so it always jumps to the
|
||||||
|
# newest release and will walk a pinned machine onto 1.4 on its own. Nothing in a
|
||||||
|
# setup script can prevent that. `bun run doctor` is where that drift is caught.
|
||||||
|
BUN_SERIES="${BUN_SERIES:-1.3}"
|
||||||
|
|
||||||
|
# The newest stable release inside the series.
|
||||||
|
#
|
||||||
|
# Asked of the npm registry rather than GitHub's releases API, which is the obvious
|
||||||
|
# source and the wrong one: it rate-limits unauthenticated callers to 60 an hour per
|
||||||
|
# IP, and it returned 504 twice while this was being written. An empty answer here
|
||||||
|
# means no bun gets installed at all (see install_bun), so where it comes from is
|
||||||
|
# load-bearing rather than a matter of taste. bun publishes every release to npm.
|
||||||
|
#
|
||||||
|
# `sort -V` rather than trusting the order the registry returns them in, and the
|
||||||
|
# pattern is anchored so 1.3.1 cannot be mistaken for the newest when 1.3.14 exists.
|
||||||
|
bun_series_latest() {
|
||||||
|
curl -fsSL https://registry.npmjs.org/bun 2>/dev/null |
|
||||||
|
jq -r '.versions | keys[]' 2>/dev/null |
|
||||||
|
grep -E "^${BUN_SERIES//./\\.}\.[0-9]+$" |
|
||||||
|
sort -V | tail -1
|
||||||
|
}
|
||||||
|
|
||||||
|
# The major.minor of a full version — "1.3.14" is in series "1.3".
|
||||||
|
bun_series_of() { sed -E 's/^([0-9]+\.[0-9]+).*/\1/' <<<"${1:-}"; }
|
||||||
|
|
||||||
|
# Where an installed bun sits relative to the pin: in, above, below, or unknown.
|
||||||
|
#
|
||||||
|
# Above and below are different problems and get different answers — above is a
|
||||||
|
# machine that has upgraded itself past the pin, below is one that is simply old —
|
||||||
|
# so this reports which rather than a yes/no. `sort -V` does the comparing, so
|
||||||
|
# nothing here has to parse a version.
|
||||||
|
bun_series_position() {
|
||||||
|
local now
|
||||||
|
now="$(bun_series_of "${1:-}")"
|
||||||
|
[[ -n "$now" ]] || {
|
||||||
|
echo unknown
|
||||||
|
return
|
||||||
|
}
|
||||||
|
[[ "$now" == "$BUN_SERIES" ]] && {
|
||||||
|
echo in
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if [[ "$(printf '%s\n%s\n' "$now" "$BUN_SERIES" | sort -V | tail -1)" == "$now" ]]; then
|
||||||
|
echo above
|
||||||
|
else
|
||||||
|
echo below
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Present anywhere: on PATH for this root shell, or in the account's own
|
# Present anywhere: on PATH for this root shell, or in the account's own
|
||||||
# ~/.bun/bin, which is where the installer puts it and where root cannot see it.
|
# ~/.bun/bin, which is where the installer puts it and where root cannot see it.
|
||||||
bun_installed() { command -v bun &>/dev/null || [[ -x "${USER_HOME}/.bun/bin/bun" ]]; }
|
bun_installed() { command -v bun &>/dev/null || [[ -x "${USER_HOME}/.bun/bin/bun" ]]; }
|
||||||
@@ -260,8 +323,8 @@ bun_version() {
|
|||||||
# The system-wide link, ensured on every run rather than only after an install.
|
# The system-wide link, ensured on every run rather than only after an install.
|
||||||
#
|
#
|
||||||
# pm2 started at boot by systemd has no login shell, so ~/.bun/bin is not on its
|
# pm2 started at boot by systemd has no login shell, so ~/.bun/bin is not on its
|
||||||
# PATH — and every one of the twenty ecosystem apps that says `script: 'bun'`
|
# PATH — and every ecosystem app that says `script: 'bun'` then fails to start on
|
||||||
# then fails to start on reboot while working perfectly when started by hand. A
|
# reboot while working perfectly when started by hand. A
|
||||||
# machine that already had bun before this script ran would never get the link if
|
# machine that already had bun before this script ran would never get the link if
|
||||||
# it were only made as part of installing.
|
# it were only made as part of installing.
|
||||||
#
|
#
|
||||||
@@ -280,8 +343,22 @@ ensure_bun_symlink() {
|
|||||||
# systemd has no login shell and therefore no ~/.bun/bin on PATH — without the
|
# systemd has no login shell and therefore no ~/.bun/bin on PATH — without the
|
||||||
# symlink every bun-based sidecar fails to start on reboot and works fine when
|
# symlink every bun-based sidecar fails to start on reboot and works fine when
|
||||||
# started by hand, which is a miserable thing to debug.
|
# started by hand, which is a miserable thing to debug.
|
||||||
|
#
|
||||||
|
# The version goes in as the installer's first positional argument, which it
|
||||||
|
# documents as "a specific tag of bun to install (e.g. bun-v0.1.4)" and turns into a
|
||||||
|
# releases/download/<tag>/ URL. `bash -s` is what carries an argument through a pipe.
|
||||||
|
# The installer handles being pointed at an older tag on purpose — it re-probes AVX2
|
||||||
|
# rather than assuming the baseline the newest release was built for — so this both
|
||||||
|
# installs and pins back down.
|
||||||
|
#
|
||||||
|
# It REFUSES to run unpinned. An empty tag means bun_series_latest could not reach
|
||||||
|
# the registry, and installing whatever is newest is the single outcome the pin
|
||||||
|
# exists to prevent, so a machine with no bun is the better failure: it is loud, and
|
||||||
|
# officer-setup's pre-flight stops on a missing bun rather than building on 1.4.
|
||||||
install_bun() {
|
install_bun() {
|
||||||
(cd / && sudo -H -u "$USERNAME" bash -c 'curl -fsSL https://bun.sh/install | bash') >/dev/null 2>&1
|
local tag="${1:-}"
|
||||||
|
[[ -n "$tag" ]] || return 1
|
||||||
|
(cd / && sudo -H -u "$USERNAME" bash -c "curl -fsSL https://bun.sh/install | bash -s '${tag}'") >/dev/null 2>&1
|
||||||
[[ -x "${USER_HOME}/.bun/bin/bun" ]]
|
[[ -x "${USER_HOME}/.bun/bin/bun" ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1981,14 +1981,18 @@ step "JavaScript runtimes"
|
|||||||
if ! skip; then
|
if ! skip; then
|
||||||
NODE_LTS="$(node_lts_major)"
|
NODE_LTS="$(node_lts_major)"
|
||||||
NODE_NOW="$(node_installed_major)"
|
NODE_NOW="$(node_installed_major)"
|
||||||
|
# Resolved before the banner so it can state the pin rather than only the version
|
||||||
|
# that happens to be here. One network call, same as node_lts_label above it.
|
||||||
|
BUN_WANT="$(bun_series_latest)"
|
||||||
|
BUN_NOW="$(bun_version)"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
info "JavaScript runtimes — required, so these are installed rather than offered"
|
info "JavaScript runtimes — required, so these are installed rather than offered"
|
||||||
echo " node ${NODE_NOW:+v$NODE_NOW }${NODE_NOW:+-> }LTS $(node_lts_label)"
|
echo " node ${NODE_NOW:+v$NODE_NOW }${NODE_NOW:+-> }LTS $(node_lts_label)"
|
||||||
echo " pm2 is a Node application, and officer-pty compiles node-pty"
|
echo " pm2 is a Node application, and officer-pty compiles node-pty"
|
||||||
echo " against whatever Node is installed — there is no Linux prebuild."
|
echo " against whatever Node is installed — there is no Linux prebuild."
|
||||||
echo " bun $(bun_installed && bun_version || echo 'not installed')"
|
echo " bun ${BUN_NOW:-not installed} — pinned to the ${BUN_SERIES} series${BUN_WANT:+, newest is ${BUN_WANT}}"
|
||||||
echo " the platform itself, and nineteen of the twenty pm2 apps."
|
echo " the platform itself, and every pm2 app except officer-pty."
|
||||||
echo " pm2 $(pm2_installed && echo installed || echo 'not installed')"
|
echo " pm2 $(pm2_installed && echo installed || echo 'not installed')"
|
||||||
echo " supervises all of them, and the ecosystem files are written for it."
|
echo " supervises all of them, and the ecosystem files are written for it."
|
||||||
echo ""
|
echo ""
|
||||||
@@ -2014,18 +2018,93 @@ if ! skip; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── bun ──
|
# ── bun, held at the pinned series ──
|
||||||
if bun_installed; then
|
#
|
||||||
ok "bun $(bun_version) already installed"
|
# Four states, because "installed" is not one answer. A machine can be inside the
|
||||||
else
|
# pin, behind it, or past it — and past it is the case worth catching, since that
|
||||||
info " installing bun..."
|
# is what a `bun upgrade` leaves behind and nothing else reports it.
|
||||||
if install_bun && bun_installed; then
|
if [[ -z "$BUN_WANT" ]]; then
|
||||||
|
# No pin resolved means no install: taking whatever bun.sh serves is the one
|
||||||
|
# thing the pin exists to stop.
|
||||||
|
warn "could not reach the npm registry to find the newest bun ${BUN_SERIES}.x"
|
||||||
|
if bun_installed; then
|
||||||
|
echo " leaving bun ${BUN_NOW} where it is"
|
||||||
|
ERRORS+=("Bun: could not resolve ${BUN_SERIES}.x; left ${BUN_NOW} alone")
|
||||||
|
else
|
||||||
|
echo " nothing installed — this will not install an unpinned bun, and"
|
||||||
|
echo " officer-setup stops on a missing bun rather than building on 1.4."
|
||||||
|
ERRORS+=("Bun: could not resolve ${BUN_SERIES}.x; nothing installed")
|
||||||
|
fi
|
||||||
|
|
||||||
|
elif ! bun_installed; then
|
||||||
|
info " installing bun ${BUN_WANT}, pinned to the ${BUN_SERIES} series..."
|
||||||
|
if install_bun "bun-v${BUN_WANT}" && bun_installed; then
|
||||||
ok "bun $(bun_version)"
|
ok "bun $(bun_version)"
|
||||||
SUMMARY+=("Bun: $(bun_version)")
|
SUMMARY+=("Bun: $(bun_version) (pinned to ${BUN_SERIES}.x)")
|
||||||
else
|
else
|
||||||
warn "the bun install did not complete"
|
warn "the bun install did not complete"
|
||||||
ERRORS+=("Bun: install failed")
|
ERRORS+=("Bun: install failed")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
else
|
||||||
|
case "$(bun_series_position "$BUN_NOW")" in
|
||||||
|
in)
|
||||||
|
if [[ "$BUN_NOW" == "$BUN_WANT" ]]; then
|
||||||
|
ok "bun ${BUN_NOW} — the newest in the pinned ${BUN_SERIES} series"
|
||||||
|
else
|
||||||
|
info "bun ${BUN_NOW} is inside the pin, and ${BUN_WANT} is out"
|
||||||
|
echo " A patch inside the series is what the pin is meant to let"
|
||||||
|
echo " through. Note that 'bun upgrade' would not stop there — it"
|
||||||
|
echo " takes the newest release, which is past the pin."
|
||||||
|
if confirm "Move to ${BUN_WANT}?"; then
|
||||||
|
if install_bun "bun-v${BUN_WANT}"; then
|
||||||
|
ok "bun $(bun_version)"
|
||||||
|
SUMMARY+=("Bun: ${BUN_NOW} -> $(bun_version)")
|
||||||
|
else
|
||||||
|
warn "the install did not complete — bun ${BUN_NOW} is untouched"
|
||||||
|
ERRORS+=("Bun: could not move to ${BUN_WANT}")
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " left at ${BUN_NOW}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
above)
|
||||||
|
# The `bun upgrade` case. Reported rather than corrected silently: moving
|
||||||
|
# somebody's runtime backwards is a decision, and this is the only place
|
||||||
|
# that knows it happened.
|
||||||
|
warn "bun ${BUN_NOW} is PAST the pinned ${BUN_SERIES} series"
|
||||||
|
echo " Most likely a 'bun upgrade', which takes no version argument and"
|
||||||
|
echo " always jumps to the newest release. The platform and every pm2"
|
||||||
|
echo " app except officer-pty run on this."
|
||||||
|
if confirm "Pin it back to ${BUN_WANT}?"; then
|
||||||
|
if install_bun "bun-v${BUN_WANT}"; then
|
||||||
|
ok "bun $(bun_version) — back inside the pin"
|
||||||
|
SUMMARY+=("Bun: pinned back, ${BUN_NOW} -> $(bun_version)")
|
||||||
|
else
|
||||||
|
warn "the install did not complete — bun ${BUN_NOW} is untouched"
|
||||||
|
ERRORS+=("Bun: could not pin ${BUN_NOW} back to ${BUN_WANT}")
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "left at ${BUN_NOW}, outside the pin"
|
||||||
|
SUMMARY+=("Bun: ${BUN_NOW}, PAST the ${BUN_SERIES} pin by choice")
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
info "bun ${BUN_NOW} is older than the pinned ${BUN_SERIES} series"
|
||||||
|
if confirm "Move to ${BUN_WANT}?"; then
|
||||||
|
if install_bun "bun-v${BUN_WANT}"; then
|
||||||
|
ok "bun $(bun_version)"
|
||||||
|
SUMMARY+=("Bun: ${BUN_NOW} -> $(bun_version)")
|
||||||
|
else
|
||||||
|
warn "the install did not complete — bun ${BUN_NOW} is untouched"
|
||||||
|
ERRORS+=("Bun: could not move to ${BUN_WANT}")
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " left at ${BUN_NOW}"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Ensured every run, not only after an install. A machine that already had bun
|
# Ensured every run, not only after an install. A machine that already had bun
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
id: infra/revisit-the-bun-1-3-pin-once-1-4-has-been
|
||||||
|
title: 'Revisit the bun 1.3 pin once 1.4 has been watched for a while'
|
||||||
|
theme: infra
|
||||||
|
status: open
|
||||||
|
created: 2026-08-22
|
||||||
|
date_source: stated
|
||||||
|
order: 6
|
||||||
|
---
|
||||||
|
|
||||||
|
**Revisit the bun 1.3 pin once 1.4 has been watched for a while.** Raised by the owner 2026-08-22,
|
||||||
|
who asked for it explicitly as a temporary hold — "a few months, until I'm sure the new 1.4 is going
|
||||||
|
to be safe" — so this exists to stop a deliberate pause becoming permanent by nobody remembering it.
|
||||||
|
|
||||||
|
bun 1.4.0 was published 2026-08-20 and `https://bun.sh/install` serves whatever is newest, so a
|
||||||
|
machine provisioned on 2026-08-22 would have put a two-day-old release under the platform and every
|
||||||
|
pm2 app except `officer-pty`. The hold is at the SERIES, not a patch: `BUN_SERIES=1.3` in
|
||||||
|
`scripts/setup/machine-setup/lib/dev.sh`, resolved to the newest `1.3.x` from the npm registry at
|
||||||
|
install time, so bug fixes inside the series still land.
|
||||||
|
|
||||||
|
Two places carry the number and they must move together — `BUN_SERIES` in `dev.sh`, and
|
||||||
|
`engines.bun` in `package.json`, which is what `bun run doctor` reads to decide whether the running
|
||||||
|
bun has drifted. bun itself ignores `engines` (verified: an install declaring `1.9.x` ran clean under
|
||||||
|
1.3.14), so that field is a declaration and doctor is the enforcement.
|
||||||
|
|
||||||
|
What is NOT covered, and is the reason a drift check exists at all: `bun upgrade` takes no version
|
||||||
|
argument — latest-stable or `--canary`, nothing else — so it always jumps to the newest release and
|
||||||
|
walks a pinned machine off the pin silently. Nothing in a setup script can prevent that; `bun run
|
||||||
|
doctor` reports it after the fact, and `machine-setup.sh --only 'JavaScript runtimes'` offers to pin
|
||||||
|
it back down.
|
||||||
|
|
||||||
|
To lift the hold: raise both numbers, run `bun run doctor` on each machine, and delete this item.
|
||||||
Reference in New Issue
Block a user