Repository navigation
chore: move to Bun 1.4.4 and replace oxfmt with bun fmt - #61
Merged
Merged
Conversation
ANT1OFF
added a commit
that referenced
this pull request
Nov 1, 2026
…h set (#60) Bumps the Claude Agent SDK, the Anthropic, MCP and AI SDKs, and the Codex side, and takes the rest of #55's set so this replaces it. Every version is at least 7 days old by npm publish time and pinned exactly. Nothing from #55 was dropped, except `oxfmt`: #61 replaced it with `bun fmt` on `main`. **Live-provider behaviour was not qualified.** No real ChatGPT or Claude account and no paid provider turn was used. Codex helpers and Dream now accept only CLI 0.164.0 on account-free evidence (below), which was the user's decision. `GRAM_LIVE_SUBSCRIPTIONS` has not been run against 0.164.0. ## Versions | Package | From | To | Newer, skipped (under 7 days old) | | --- | --- | --- | --- | | `@anthropic-ai/claude-agent-sdk` | 0.3.302 | **0.3.310** | 0.3.311–0.3.316 | | `@anthropic-ai/sdk` | 0.135.0 | **0.137.0** | none in 0.x; 1.0.0–1.3.0 are a major, out of scope | | `@modelcontextprotocol/sdk` | 1.32.1 | **1.32.2** | none | | `ai` | 7.0.143 | **7.0.153** | 7.0.154–7.0.159 | | `@ai-sdk/openai-compatible` | 3.0.71 | **3.0.78** | 3.0.79–3.0.82 | | Codex CLI (bindings and bundled runtime) | 0.162.1 | **0.164.0** | 0.165.0, 0.166.0 | | `@tanstack/react-virtual` | 3.14.21 | **3.14.22** | 3.14.23, 3.14.24 | | `@tanstack/react-query` | 5.104.4 | **5.104.5** | 5.105.0 | | `@tanstack/react-router` | 1.170.45 | **1.170.46** | none | | `@tanstack/router-plugin` | 1.168.47 | **1.168.48** | none | | `streamdown` | 2.7.0 | **2.7.1** | none | | `@babel/core` | 8.0.7 | **8.0.9** | 8.0.10 | | `lucide-react` | 1.59.0 | **1.61.0** | 1.62.0–1.66.0 | | `motion` | 14.3.0 | **14.6.0** | 14.7.0–14.9.0 | | `oxlint` | 1.88.0 | **1.89.0** | 1.90.0 | | `oxlint-tsgolint` | 7.0.2004 | **7.0.2005** | none | | `shadcn` | 4.22.0 | **4.22.1** | 4.22.2, 4.23.0 | | `vite` | 8.3.4 | **8.3.5** | none | | `zustand` | 5.0.15 | **5.0.16** | none | `ai` and `@ai-sdk/openai-compatible` land one patch past #55 because those versions passed the 7-day line. `bun.lock` was regenerated with pinned Bun 1.4.4 (#61 landed mid-PR) and `--minimum-release-age 604800`, so transitive packages obey the rule too: a plain install pulled `rolldown` 1.2.16, `postcss` 8.5.32 and `@babel/*` 8.0.10, all too new. All 82 packages new to the lockfile against `main` were then checked against npm publish times. The catalog keeps its keys and only the values listed above change. Native overrides, the Effect patch, `.bun-version` (beyond #61 on `main`), Node and Electron packages are unchanged. ## What "the Codex side" is There is no Codex npm dependency, and none was added. Gram pins the CLI in `CODEX_CLI_VERSION` and commits generated bindings, so moving it means: - `packages/agents/src/codex/protocol`: regenerated with `codex app-server generate-ts` from the real 0.164.0 in an empty `CODEX_HOME` (24 files changed, 16 added). - `codex-baseline.ts`: the managed runtime's npm packages for all four targets. Integrity is computed from the downloaded tarballs and matches the registry's. - `CODEX_HELPER_VERSION` and `VERIFIED_CODEX_DREAM_VERSION`, the exact-match gates, move to 0.164.0, with their messages, docs and synthetic fixtures. **User-visible effect:** Codex 0.162.x is now a release line below the baseline, so its verdict goes from `supported` to `unsupported`, shown in the error tone with an upgrade step, and helpers and Dream refuse it. The verdict blocks no session, so ordinary Codex chats still run. `codexVersionWarning` warns there too. ## Changelog notes that matter to Gram **Codex 0.163.0 and 0.164.0** - Protocol: additive for Gram; the adapter typechecks unchanged. `thread/readState/changed` falls to the mapper's default. `sci` and `sci_plus` plans and the `preModelRequest` and `postModelRequest` hook events reach no Gram mapping. `ImageGenerationItem.result` is now optional, and Gram doesn't read it. - Resume now restores saved permissions, network grants and model settings. Gram sends explicit thread settings on `thread/resume`, which the changelog says are respected. Not exercised against a real saved thread. - Instant interrupt is on by default, and input admission closes while a turn finishes recording. A steer in that window could be refused more often than before; Gram surfaces a refused steer as an error, as it already did. Not changed here. - Failed compaction no longer leaves partial summaries, and incomplete responses are terminal errors. - **`stable_environment_tools` is `removed` in 0.164.0 and its "on" behaviour is now permanent**, while `config/read` still echoes the `false` pin, so that check proved nothing. With Gram's exact helper params on `gpt-5.5`, 0.164.0 offered `apply_patch` and `view_image`, which 0.162.1 did not. An independent review found this; I reproduced it. The fix: the dead pin is gone and `view_image` is pinned off. No key found removes `apply_patch` (`include_apply_patch_tool`, `tools.apply_patch` and `apply_patch_tool_type` had no effect), so it stays declared on that model, and the reviewer's fake-model call got "No usable execution environment is available", wrote nothing and returned no image data. `environments: []` is now the only control on it. Wording that said environment tools are disabled now says helpers run with no environment. - `executor_capability_discovery_v2`, `multi_env_copy_file` and `request_permissions_tool` are also pinned off. They are opt-in, but a user's `config.toml` could enable them for helper threads. - The bundled model catalog is identical between 0.162.1 and 0.164.0. - Thread-ownership behaviour was measured at 0.162.1 and not re-measured. Its comments and docs now say so rather than claiming 0.164.0. The fence stays conservative either way: only owning-child exit clears it. **Agent SDK 0.3.303–0.3.310 and Claude Code 2.1.303–2.1.310** - `sdk.d.ts` adds no exported types and no message subtypes, and model ids are unchanged. `CLAUDE_AGENT_SDK_VERSION` moves to 0.3.310, so the baseline CLI is 2.1.310. The bundled darwin-arm64 CLI reports that version. - Permission and sandbox hardening arrives with no Gram change, including: Bash `cd`-then-git prompts, `Edit` deny rules against redirects, sandboxed startup files, `--mcp-config` write protection, a disabled MCP server no longer reconnecting on a later prompt in SDK sessions, and Windows PowerShell hooks no longer failing open. - 0.3.305 stops `setMcpServers` servers being replaced by same-named user servers, and 0.3.309 fixes `managedSettings` dropping `disableAllHooks`. Gram neither calls `setMcpServers` nor passes `managedSettings`, so neither fix reaches it. **Worth a follow-up, not built here:** Codex `excludedThreadIds`, durable thread read state, `thread/sample`, the `thread/fork` prediction mode and `toolListMeta`. Agent SDK `wakeup_source` on `command_lifecycle`, usage `recommendations`, and stable `getContextUsage()` row ids. ## Test change `resolve.test.ts` used `patch(CODEX, -1)` as "an older Codex", which at x.y.0 gives `0.164.-1`, an unparsable version. Nothing weakened: the Codex tests now use `minor(CODEX, -1)` and assert the stricter `unsupported` verdict, and the `graceful` case is asserted through Claude, whose patch number is not zero. ## Verification | Command | Result | | --- | --- | | `bun run check:scaffold`, `check:docs`, `check-dependency-pins`, `oxlint --deny-warnings`, `fmt:check`, `tsc -b` | pass | | `vitest run packages/agents` plus server `agent-runtime`, `agent-support`, `helper`, `launch-checks`, `gateway`, `codex-dream-protocol` | 70 files passed, 9 skipped; 1169 tests passed, 25 skipped | | `GRAM_LIVE_CODEX_RUNTIME=1 vitest run managed-codex.live.test.ts` | Real 0.164.0 fetched from npm, verified, installed to a temp store and handshaken in an empty home. No account. The Windows case is skipped. | | `bun run --filter @gram/web build` | pass | | `bun install --frozen-lockfile` (Bun 1.4.4) | pass | Account-free helper probe: real app-servers with Gram's helper flags and `thread/start` and `turn/start` params including `environments: []`, an empty home, a fake key and a loopback Responses endpoint. `config/read` reads back every pin as `false`, `web_search` disabled, `agents.enabled` false and the probe's base URL. Tools offered: | CLI, pins, model | Tools | | --- | --- | | 0.162.1, old pins, `gpt-5.5` | `request_user_input`, `get_goal`, `create_goal`, `update_goal` | | 0.164.0, old pins, `gpt-5.5` | the above + `apply_patch`, `view_image` | | 0.164.0, this PR, `gpt-5.5` | the above 4 + `apply_patch` | | 0.164.0, this PR, `gpt-6-luna` | none | CI can't catch this class of change: the fixtures are synthetic and no test runs a real CLI's tool list. Independent review: baseline hashes, target mapping and lockfile scope were confirmed against the registry on `fe7fdc3`. The helper fix was re-reviewed on `8f0c6fb`, and all 23 pins read back `false` on real 0.164.0. Later commits merge `main` (#61), reflow one note and tighten one comment. ## Known limits - Not tested: real ChatGPT auth and the built-in route, any real provider turn, Dream on a real account, resume of a real saved thread, Windows and Intel Mac runtime installs (their hashes are verified, the binaries were not run). - The probe used a loopback endpoint, not the helper's real base URL. - `apply_patch` stays declared to the model on `gpt-5.5`, so a helper turn can spend an extra provider round on a call that fails. - Not fixed here, not caused by this bump: `helper.ts` sends `model_reasoning_effort: null` when a route has no effort, and real 0.162.1 and 0.164.0 both reject that at `thread/start` ("reasoning_effort must not be empty"). A Codex helper call without an effort fails on either version. - Full `bun run check` and e2e were left to CI and the managed check, per the shared-Mac rule. - The bindings are generated from the darwin-arm64 CLI.
ANT1OFF
added a commit
that referenced
this pull request
Nov 1, 2026
## Why Since #61, `bunfig.toml` has `minimumReleaseAge = "1d"`. Dependabot's own Bun reads `bunfig.toml` and cannot parse unit strings, so every Dependabot Updates run fails on that file. Run 41319933398 failed all 18 dependencies with: ``` error: Expected number of seconds for minimumReleaseAge at .../bunfig.toml:4:21 Invalid Bunfig: failed to load bunfig ``` Bun 1.4.2 and 1.4.3 reject the string too. ## Change `minimumReleaseAge = 86400` with a one-line comment. A plain number is seconds in every Bun version, so the policy stays one day. Nothing else in the file changed, and no docs or tests repeat `"1d"`. [docs/VERIFICATION.md](docs/VERIFICATION.md) describes the policy as "a day" and does not say Dependabot skips `bunfig.toml`, so it needed no edit. ## Verification | Command | Result | | --- | --- | | `bun install --frozen-lockfile` (Bun 1.4.4) | passes; `bun.lock` unchanged (only `bunfig.toml` in the diff) | | Bun 1.4.3, `install --dry-run --ignore-scripts` on a copy with `"1d"` | fails: `Expected number of seconds for minimumReleaseAge` / `Invalid Bunfig: failed to load bunfig`, exit 1 | | Bun 1.4.3, same on a copy with `86400` | passes, exit 0 | | `bun run check:scaffold` | Scaffold boundaries pass (3767 text files checked) | | `bun run check:docs` | passed (35 tracked Markdown files) | | `bun run fmt:check` | all matched files use the correct format | The old-Bun copies held `package.json`, `bun.lock`, `patches/` and `bunfig.toml`. They ran with `--ignore-scripts` because the root `postinstall` needs the full source tree. ## Known limits - Dependabot cannot be triggered from here. Its next scheduled Monday run is the real proof that the Bun it uses now parses the config. - Bun 1.4.3 was checked by dry run only, not a full install. - Root configuration runs every CI lane, so CI is the full check. I did not run `bun run check` locally. - No release is cut.
ANT1OFF
added a commit
that referenced
this pull request
Nov 2, 2026
## Why The disk was nearly full and 66 Gram worktrees had piled up, mostly from finished, merged and archived workers. The sweep had already freed most of their `node_modules`, but it never removed them. Two causes showed up on a real machine: | Leftovers | Why the sweep kept them | |---|---| | ~40 archived workers whose PR **merged** (e.g. #60, #61, #98, #99) | Once GitHub answers that an archived thread's PR merged, the publisher drops that thread's state. `badgeFor` then returns `undefined`, `workerPrBadges` gives `[undefined]`, `anyPrOpen` reads the unknown PR as open, and `mergedAndDone` is false. So the 10-minute `MERGED_GRACE_MS` never applied, and every merged archived worker waited the 14-day `removeAfterDays`. All of the Gram 1.x leftovers were 7 days old or less. | | ~15 archived workers with **no PR of their own** (reviewers, research, release cuts on a detached HEAD) | `remove` needs `hasPr`, so these were only ever freed. A test named it a rule: "never removes a worker with no PR". | Not gaps, checked and left alone: - `local-checks` is already pruned by `local-checks.ts` retention: passes are kept 30 days and other checks 7, logs are capped at 256 MB, and abandoned work folders are reclaimed after an hour. On this machine it was 57 MB with `work/` empty. - Worktrees from the 0.x app (gram-private) belong to another app's store. The 1.x sweep never knew about them. ## What changes - **Archived merged workers**: a parked thread completed on a merge (`completedReason: "prMerged"`, `completedAt` set) that follows no extra PRs and has no badge still open counts as merged-done. It goes after `MERGED_GRACE_MS`. `checkWorktree` still asks GitHub about the bound PR afresh and needs it merged or closed, the worktree clean, and HEAD on a remote. - **Archived workers with no PR**: a parked worker with no bound PR, no badge and no extra PRs gets `remove` a day (`ARCHIVED_GRACE_MS`) after its last activity, marked `withoutPr`. `sweepStep` removes it only when it's clean, HEAD is on a remote, and GitHub answered that no PR is open for its branch (`noPrOpen`). A failed lookup keeps it. Plain (non-worker) threads with no PR still aren't removed, and neither are unarchived workers. - **A process working in the worktree keeps it.** Right before it frees `node_modules` or removes a worktree, the sweep lists every process's working folder: `lsof -d cwd` on macOS, `/proc/*/cwd` on Linux. If any is inside the worktree, it does nothing (`inUse` / `freeUnlessInUse`). If it can't tell (Windows, a failed scan), it also does nothing. Thread state alone can't see an agent still running after an archive, a dev server or an outside shell. - The Settings › Storage toggle copy names the new rule. Nothing else changed: removal still goes through `closeAtRest`, which refuses a dirty tree, a busy thread or a running terminal. Threads held by others, threads waiting on the user and orchestrators are still skipped, and no branch is ever deleted. ## Independent review fixes (findings 1–5) | # | Finding | Fix | Refusal test | |---|---|---|---| | 1 | An adopted worker in a user-made worktree outside `.worktrees` got removed | Every thread, workers included, is swept only directly inside the repo's own `.worktrees/` | Adopted worker outside, merged worker elsewhere, nested folder and unknown repo are never due | | 2 | `status.showUntrackedFiles=no` hid untracked files from the clean check and from `git worktree remove` | `STRICT_STATUS_ARGS` (`--untracked-files=all --ignore-submodules=none --ignored=no`) in the sweep, the Storage probe and `worktreeCleanliness`; `git worktree remove` runs with `-c status.showUntrackedFiles=all` | Real repo with the config set: the sweep reads it dirty, `removeWorktreeDir` refuses, the file survives | | 3 | `Pr.status` returns `undefined` when `gh` isn't ready, which read as "no PR open" | No-PR removal needs `gh pr list --state open` itself to succeed and list none (by branch, or by commit when detached). `Pr.status` no longer feeds it | Signed out, missing, timed out, garbage or open PR: kept | | 4 | Closing the thread deleted its pending wakes | A thread with a wake isn't removed (checked when judged and again right before the close); freeing still runs | No-PR and merged threads with a wake: removal not due | | 5 | The grace counted from last activity, not from the archive | `Thread.parkedAt`, stamped by the store when an edit archives a thread and dropped on unarchive; grace counts from max(activity, parkedAt). Threads archived before this wait the full M days | Quiet 3 days, archived 1 h ago: not due. Store test for the stamp | Also from the review's lower-severity notes: a detached HEAD is kept under `refs/gram/swept/<thread id>` before its worktree goes (if that can't be written, the worktree stays), a free step scans processes once instead of twice, and the Storage copy is fixed. For each finding, putting the old line back makes its new test fail. ## Verification - At 1a656a2: `bunx vitest run packages/contracts packages/git packages/server/src/git packages/server/src/persistence apps/web/src/settings`: 179 files, 1935 passed, 16 skipped. lint, typecheck, fmt:check, check:scaffold pass. - `bunx vitest run packages/server/src/git/`: 33 files, 326 passed, 13 skipped. `worktree-gc.test.ts` is 19/19. - Live-process test: a real `sleep` runs inside a temp worktree's `node_modules`. The sweep's free step leaves every file in place, and frees the folder once the process exits. With the guard line taken out, this test fails. - A worker in the same state as `feat-voice-vocab` (open, cold between turns, no PR, never completed) is asserted never due. - `bunx vitest run packages/server/src/git/ apps/web/src/settings`: 76 files, 682 passed, 13 skipped - The new tests run against the old `worktree-gc.ts` fail 6 of 15, including "removes an archived merged worker after the grace, though its badge was dropped". With the fix, 15/15 pass. - `bun run typecheck`, `bun run lint`, `bun run fmt`, `bun run check:scaffold` pass. - Full `bun run check` is left to CI and the merge gate. The machine is shared. ## Known limits - A reviewer worktree on a detached HEAD whose PR branch was deleted and pruned from the remote reads as unpushed and is kept. That's the safe side. It didn't happen on this machine, so it isn't handled here. - Ignored files (e.g. `.env.local`, notes in ignored folders) aren't counted by any clean check and go with `git worktree remove`, as they already did for the close dialog and Storage cleanup. - The process guard sees only processes whose working folder is inside the worktree, not ones holding files open there from elsewhere (an editor, a `bun --cwd` run). An idle thread's live agent session or a shell left at its prompt keeps its worktree until the session is evicted. - Removal closes the thread. A branch it had stays, and a detached HEAD is kept under `refs/gram/swept/`. `restore_worktree` can't bring back a worktree that had no branch. - On Windows the process guard can't tell, so the sweep acts on nothing there. No Windows releases are published. - The process check, like the thread check, runs right before the step and isn't held across the deletion (the existing "known and accepted gap"). - Not live-qualified against a running app. Covered by unit tests and git-backed tests with a stubbed `gh`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Moves the repo to Bun 1.4.4 (the Mac runs it through ANT1OFF/nixconf#22), adds a one-day minimum release age, and replaces oxfmt with the built-in
bun fmt..bun-version,packageManagerandengines.bunare now 1.4.4..node-versionalready says 26.12.0, which agrees with nixconf, so it is unchanged.bunfig.toml: addsminimumReleaseAge = "1d". NominimumReleaseAgeExcludes, because nothing pinned needs one.fmtandfmt:checknow runbun fmt --no-script. oxfmt is gone from the catalog and devDependencies, its$schemaline is gone from.oxfmtrc.json, and the two comments naming it are updated.docs/VERIFICATION.mdsays what the release age covers..oxfmtrc\.jsonstays intools/check/affected.mjs'sGLOBALlist, because the file is still the formatter config, so a change to it still affects every workspace. The1.4.3strings incheck-slot.test.mjsandlocal-checks.test.tsbelong to synthetic fixtures that are consistent on their own.What moved in
bun.lockBump commit: no resolved version changed. The set of
name@versionvalues is identical before and after, andoverrides,catalog,patchedDependenciesandtrustedDependenciesare untouched.shadcn,@shadcn/registry,electron-vite,@tanstack/router-pluginandbabel-dead-code-eliminationpull in used to sit at the root, each with a private@babel/core@7copy, because the root@babel/coreis 8. They now sit under the parents that use them.1.2.2workspace version also became1.3.0.Swap commit: removes oxfmt, its 21 platform bindings and
tinypool, which only oxfmt used. Nothing else changes.Risks from the 1.4.4 notes
NodeChildProcessSpawner.js.--frozen-lockfile, and--prefer-offline --filterinpr-shots.mjs. Nobun x,bunxorbun pmcalls exist. Thepr-shots.mjscombination ran with exit 0 on 1.4.4.apps/*,packages/*and--filter @gram/...contain no*(...)or{a..b}forms.hasInstallScriptrecord.rbonly readsdependenciesfrom that object.check-dependency-pins.mjsdoesn't readbun.lock.bun.lockand.bun-version, with norestore-keys, and this PR changes both."1d"on older BunExpected number of seconds), so this value and the 1.4.4 pin must land together. Dependabot's Bun is 1.4.2, but its file fetcher takes only.npmrc, neverbunfig.toml, so it isn't affected.rc.mjscan't fail on it. A plainbun installdoes refuse (blocked by minimum-release-age).Checks run
All local on Bun 1.4.4 and Node 26.12.0, using synthetic state. No live-provider or packaged-app qualification.
bun install --frozen-lockfilefrom a fresh clone with an emptyBUN_INSTALL_CACHE_DIR(on the bump commit): exit 0, no warnings, working tree clean.bun install --frozen-lockfileon the final tree: no changes.bun run check:scaffold,bun run check:docs,bun run lint,bun run typecheck: pass.bun run fmt:check: 2858 files, 67 ms.bun run fmtwrote no diff, and--doctorreports all 2858 formatted natively.printWidth, and a file under anignorePatternspath is skipped (exit 0).bun run test tools/check tools/release-rc packages/themes: 15 files, 269 tests pass.bun run build:bundles: pass, including third-party notices, which exercises the re-nested Babel packages.The full
bun run check, e2e and packaging are left to CI and the managed check, as this is a shared Mac.Known limits
main. To revert it alone, usegit reverton c80ded7 (kept by the PR's head ref) and regeneratebun.lock.bun.lockand resolving from scratch in a stripped copy didn't finish within 5 minutes, and I didn't find out why. The lock here comes frombun installover the existing file, which is also what keeps versions stable.Dependencies or holds
#60 (
feat/agent-deps-bump) also touchesbun.lockandpackage.json. Neither PR has to merge first. Whichever lands second rebases and regeneratesbun.lock; its bumps are not redone here.