Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/setup-xum/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ runs:
- name: Setup Bun
uses: oven-sh/setup-bun@b7a1c7ccf290d58743029c4f6903da283811b979 # v2.1.0
with:
bun-version: 1.3.12
bun-version: 1.4.3

- name: Get Bun version
id: bun-version
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/auto-cleanup-fixup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ jobs:
- uses: oven-sh/setup-bun@b7a1c7ccf290d58743029c4f6903da283811b979 # v2.1.0
if: ${{ steps.precheck.outputs.enabled == 'true' }}
with:
bun-version: 1.3.12
bun-version: 1.4.3

# Pin the git identity so the AI agent doesn't invent its own.
- name: Configure git identity
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/auto-cleanup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
- uses: oven-sh/setup-bun@b7a1c7ccf290d58743029c4f6903da283811b979 # v2.1.0
if: ${{ steps.precheck.outputs.enabled == 'true' }}
with:
bun-version: 1.3.12
bun-version: 1.4.3

# Pin the git identity so the AI agent doesn't invent its own.
# Without this, commits end up as "mux-auto-cleanup[bot]" while the
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ jobs:
echo "sha=$sha" >>"$GITHUB_OUTPUT"
- uses: oven-sh/setup-bun@b7a1c7ccf290d58743029c4f6903da283811b979 # v2.1.0
with:
bun-version: 1.3.12
bun-version: 1.4.3
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
with:
enable-cache: false
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/update-models.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ jobs:
- uses: oven-sh/setup-bun@b7a1c7ccf290d58743029c4f6903da283811b979 # v2.1.0
if: ${{ steps.precheck.outputs.enabled == 'true' }}
with:
bun-version: 1.3.12
bun-version: 1.4.3

- name: Update models.json
if: ${{ steps.precheck.outputs.enabled == 'true' }}
Expand Down
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1 +1 @@
bun 1.3.12
bun 1.4.3
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ WORKDIR /app
ARG RELEASE_TAG

# Install bun (used for package management and build tooling)
RUN npm install -g bun@1.3.12
RUN npm install -g bun@1.4.3

# Install git (needed for version generation) and build tools for native modules
# bzip2 is required for lzma-native to extract its bundled xz source tarball
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/terminal_bench/mux_setup.sh.j2
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export PATH="${BUN_INSTALL}/bin:${PATH}"
if ! command -v bun >/dev/null 2>&1; then
log "installing bun"
# Pin the repo's Bun (package.json packageManager); an unpinned install floats to the latest major.
curl -fsSL "${MUX_BUN_INSTALL_URL:-https://bun.sh/install}" | bash -s "bun-v1.3.12"
curl -fsSL "${MUX_BUN_INSTALL_URL:-https://bun.sh/install}" | bash -s "bun-v1.4.3"
fi

MUX_APP_ROOT="${MUX_APP_ROOT:-/opt/mux-app}"
Expand Down
5 changes: 1 addition & 4 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion docs/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ Freely make breaking changes, and reorganize / cleanup IPC as needed.

## Run a Bug Bash (tests/bugbash)

- Host: Linux on amd64, not root, with a local rootful Docker socket. The image is `linux/amd64`, so the sandbox is amd64 only: the runner refuses another architecture before the pull. Docker Desktop, rootless Docker and a remote endpoint refuse. The first launch pulls the pinned sandbox image (`tests/bugbash/sandbox/image.json`, about 1.4 GB). Node.js and Chromium come with the image: the host needs neither for these targets. The host needs git, make and Bun 1.3.12 (`packageManager` in `package.json`): `make` builds with Bun and runs the launchers with it. Check it with `bun --version`. On a fresh checkout, run `bun install` first.
- Host: Linux on amd64, not root, with a local rootful Docker socket. The image is `linux/amd64`, so the sandbox is amd64 only: the runner refuses another architecture before the pull. Docker Desktop, rootless Docker and a remote endpoint refuse. The first launch pulls the pinned sandbox image (`tests/bugbash/sandbox/image.json`, about 1.4 GB). Node.js and Chromium come with the image: the host needs neither for these targets. The host needs git, make and Bun 1.4.3 (`packageManager` in `package.json`): `make` builds with Bun and runs the launchers with it. Check it with `bun --version`. On a fresh checkout, run `bun install` first.
- Env: `ANTHROPIC_API_KEY`, `ANTHROPIC_BASE_URL` (with or without `/v1`) and `BUGBASH_BUDGET_USD` (the most the run may spend at list price, in dollars). The runner reads the key only from `ANTHROPIC_API_KEY`: if your key is in another variable (for example `ANTHROPIC_AUTH_TOKEN`), copy it there. Only `anthropic:<model>` explorers run.
- Run: `make bug-bash BUGBASH_BUDGET_USD=9`. It checks the env, Docker and the image, then builds, then launches the jobs. The checks take about a second once the image is cached. The first run pulls about 1.4 GB, which takes minutes. Defaults: the 6 charters of `tests/bugbash/charters.txt` × `anthropic:claude-sonnet-5-5` = 6 jobs, `--parallel 8`, `--max-steps 6`, `BUGBASH_EFFORT=medium`, and `BUGBASH_AI=auto`: one probe decides the app AI. It is Haiku 4.5 when the probe answers, and the mock when the upstream is busy (429 or 5xx). In every mode, two probe results refuse before any job: an unreachable `ANTHROPIC_BASE_URL` (`cannot reach ANTHROPIC_BASE_URL (<host>)`, at $0) and a 401 or 403 (`ANTHROPIC_API_KEY rejected by <host>`). On a 403, the refusal also names the probed model and the variable that picks it (`BUGBASH_APP_MODEL`, or a `BUGBASH_MODELS` entry when the app AI is the mock): check the key's access to that model too.
- Cost: before any job, the run prints its estimate and a recommended budget, and refuses a lower `BUGBASH_BUDGET_USD` with the number to set. The default needs $9: $0.45 per Sonnet charter (measured at 6 steps) plus headroom for the calls in flight. A higher `BUGBASH_EFFORT` and a `BUGBASH_APP_MODEL` other than Haiku 4.5 raise it, as marked guesses (high effort: $12). It takes about 5 minutes after the build. Adding Opus (`BUGBASH_MODELS=anthropic:claude-sonnet-5-5,anthropic:claude-opus-5-5`) needs about $29.
Expand Down
2 changes: 1 addition & 1 deletion docs/guides/github-actions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ jobs:
- uses: oven-sh/setup-bun@b7a1c7ccf290d58743029c4f6903da283811b979 # v2.1.0
if: ${{ steps.precheck.outputs.enabled == 'true' }}
with:
bun-version: 1.3.12
bun-version: 1.4.3
# Pin the git identity so the AI agent doesn't invent its own.
# Without this, commits end up as "mux-auto-cleanup[bot]" while the
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"publishConfig": {
"access": "public"
},
"packageManager": "bun@1.3.12",
"packageManager": "bun@1.4.3",
"scripts": {
"dev": "make dev",
"prebuild:main": "./scripts/generate-version.sh",
Expand Down Expand Up @@ -221,6 +221,7 @@
"mitata": "1.0.34",
"nodemon": "^3.1.10",
"playwright": "^1.56.0",
"playwright-core": "1.57.0",
"postcss": "^8.5.6",
"posthog-js": "^1.276.0",
"prettier": "^3.6.2",
Expand Down
2 changes: 1 addition & 1 deletion packages/chat-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@coder/mux-chat-components",
"version": "0.1.0",
"description": "Shared chat components for Xum conversation rendering",
"packageManager": "bun@1.3.12",
"packageManager": "bun@1.4.3",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion src/browser/stores/WorkspaceConsumerManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const STATS: ChatStats = {
usageHistory: [],
};

// bun-types (^1.2.23) lags the pinned runtime (bun@1.3.12), which implements this.
// bun-types (^1.2.23) lags the pinned runtime (bun@1.4.3), which implements this.
const fakeTimers = jest as typeof jest & { advanceTimersByTime: (ms: number) => void };

// The manager treats a calculation as slow after this long (WorkspaceConsumerManager.ts).
Expand Down
22 changes: 20 additions & 2 deletions src/common/orpc/onChatCursorFingerprint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,24 @@ export const FNV_OFFSET_BASIS = 0x811c9dc5;
const FNV_PRIME = 0x01000193;
export const MISSING_TIMESTAMP = -1;

/**
* Prior-row order of both fingerprint twins. It must be a total order: persisted metadata is not
* schema-checked, so a sequence can be NaN, Infinity or a string, and `NaN || tiebreak` made the
* old comparator inconsistent. Array.prototype.sort leaves the result of an inconsistent
* comparator to the engine, and on Bun 1.4.3 the same call site sorted such rows differently once
* the JIT optimized it. Non-finite sequences therefore sort after every finite one, then by id.
*/
export function comparePriorHistoryOrder(
leftSequence: number,
leftId: string,
rightSequence: number,
rightId: string
): number {
const leftFinite = Number.isFinite(leftSequence);
if (leftFinite !== Number.isFinite(rightSequence)) return leftFinite ? -1 : 1;
return (leftFinite ? leftSequence - rightSequence : 0) || leftId.localeCompare(rightId);
}

export function updateFnv1a(hash: number, value: string): number {
let next = hash >>> 0;
for (let index = 0; index < value.length; index += 1) {
Expand Down Expand Up @@ -59,8 +77,8 @@ export function computePriorHistoryFingerprint(
return undefined;
}

priorEntries.sort(
(left, right) => left.historySequence - right.historySequence || left.id.localeCompare(right.id)
priorEntries.sort((left, right) =>
comparePriorHistoryOrder(left.historySequence, left.id, right.historySequence, right.id)
);

let hash = FNV_OFFSET_BASIS;
Expand Down
20 changes: 18 additions & 2 deletions src/node/orpc/replayOwnership.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect, test, spyOn } from "bun:test";
import type { AsyncLocalStorage } from "node:async_hooks";
import { AsyncLocalStorage } from "node:async_hooks";
import { EventEmitter } from "node:events";
import { createAgentSessionHarness } from "../services/agentSession.testHarness";
import { subscribeWorkspaceChat } from "./routerSubscriptions";
Expand Down Expand Up @@ -247,6 +247,21 @@ test("an aborted replay cannot publish its delayed window to another subscriber"
}
});

// Bun 1.4.2 and 1.4.3: AsyncLocalStorage.disable() hides the store from synchronous code but not
// from a continuation that resumes after it. Node and Bun 1.3.12 hide it there too, and the
// product runs on Node, so the release is asserted only where the runtime implements it.
async function disableHidesResumedStore(): Promise<boolean> {
const storage = new AsyncLocalStorage<object>();
const gate = Promise.withResolvers<void>();
const seen = storage.run({}, async () => {
await gate.promise;
return storage.getStore();
});
storage.disable();
gate.resolve();
return (await seen) === undefined;
}

test("disposing a held replay releases its async context without affecting a sibling session", async () => {
const resumed = Promise.withResolvers<unknown>();
const h = await setup(() => resumed.resolve(publication.getStore()));
Expand All @@ -260,7 +275,8 @@ test("disposing a held replay releases its async context without affecting a sib
await h.gates[0].entered.promise;
await h.session.dispose();
h.gates[0].release.resolve();
expect(await resumed.promise).toBeUndefined();
const resumedStore = await resumed.promise;
if (await disableHidesResumedStore()) expect(resumedStore).toBeUndefined();
await held.caught;
expect(held.events.some((event) => event.type === "stream-delta")).toBe(false);
const surviving = sibling.subscribe();
Expand Down
16 changes: 8 additions & 8 deletions src/node/runtime/LocalBaseRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { spawn } from "child_process";
import * as fs from "fs";
import * as fsPromises from "fs/promises";
import * as path from "path";
import { Readable, Writable } from "stream";
import type { Readable } from "stream";
import { Writable } from "stream";

import type {
Runtime,
Expand Down Expand Up @@ -35,6 +36,7 @@ import { getErrorMessage } from "@/common/utils/errors";
import { getAtomicWriteTempPath } from "./atomicWriteTempPath";
import { buildShellExport } from "./shellEnv";
import { sanitizeXumChildEnv } from "./childProcessEnv";
import { nodeReadableToWeb } from "@/node/utils/nodeReadableToWeb";

/**
* Acquire a regular file for ReadFileOptions.requireRegularFile.
Expand Down Expand Up @@ -121,8 +123,7 @@ function wrapNodeReadable(
start: async (controller: ReadableStreamDefaultController<Uint8Array>) => {
try {
const nodeStream = await acquire();
// eslint-disable-next-line local/no-chained-type-assertions -- grandfathered when the rule was introduced; fix the underlying type instead of copying this pattern
const webStream = Readable.toWeb(nodeStream) as unknown as ReadableStream<Uint8Array>;
const webStream = nodeReadableToWeb(nodeStream);
reader = webStream.getReader();
if (cancelled || abortSignal?.aborted) {
// Landed while acquiring (reader did not exist yet): release the source now.
Expand Down Expand Up @@ -283,11 +284,10 @@ export abstract class LocalBaseRuntime implements Runtime {
// Wrap in DisposableProcess for automatic cleanup
const disposable = new DisposableProcess(childProcess);

// Convert Node.js streams to Web Streams
// eslint-disable-next-line local/no-chained-type-assertions -- grandfathered when the rule was introduced; fix the underlying type instead of copying this pattern
const stdout = Readable.toWeb(childProcess.stdout) as unknown as ReadableStream<Uint8Array>;
// eslint-disable-next-line local/no-chained-type-assertions -- grandfathered when the rule was introduced; fix the underlying type instead of copying this pattern
const stderr = Readable.toWeb(childProcess.stderr) as unknown as ReadableStream<Uint8Array>;
// Convert Node.js streams to Web Streams. Not Readable.toWeb: on Bun 1.4.3 it can throw an
// uncaught error when the bash tool cancels a reader on output overflow (nodeReadableToWeb).
const stdout = nodeReadableToWeb(childProcess.stdout);
const stderr = nodeReadableToWeb(childProcess.stderr);
// eslint-disable-next-line local/no-chained-type-assertions -- grandfathered when the rule was introduced; fix the underlying type instead of copying this pattern
const stdin = Writable.toWeb(childProcess.stdin) as unknown as WritableStream<Uint8Array>;

Expand Down
2 changes: 1 addition & 1 deletion src/node/runtime/sshConnectionPool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
} from "./sshConnectionPool";
import { isPermanentSSHFailure } from "./Runtime";

// bun-types (^1.2.23) lags the pinned runtime (bun@1.3.12), which implements this.
// bun-types (^1.2.23) lags the pinned runtime (bun@1.4.3), which implements this.
const fakeTimers = jest as typeof jest & { advanceTimersByTime: (ms: number) => void };

describe("sshConnectionPool", () => {
Expand Down
Loading
Loading