· 9 min read · Zach Geier · Blog post #2

Dev log #1: six weeks since launch

The launch post went up on June 22. Since then roughly 190 commits have landed across the two repos that make up Oak — oak/oak (the open-source core and CLI) and oak/oakspace (the server behind oak.space) — and the CLI went from v0.99 to v0.102.

That launch post ended with a list of things Oak didn’t have: no Windows build, no CI, no issues, no comments. Two of those are now gone. Here’s what actually happened, in roughly the order it mattered.

Oak runs on Windows

The Windows port landed the same day as the launch post and has been filled in since. Install it from PowerShell:

irm https://oak.space/install.ps1 | iex

That drops oak.exe into %USERPROFILE%\.local\bin and puts it on your PATH. Clone, push, pull, commit, branch and merge all work. oak mount works too, on top of the Projected File System instead of FUSE/FSKit — it’s an optional Windows feature you enable once per machine.

The Unix installer also serves native Intel-macOS binaries now instead of quietly handing Rosetta an ARM build. Supported today: macOS on Apple Silicon and Intel, Linux x86_64, Windows x86_64. Linux ARM64 binaries are published but the installer doesn’t pick them yet.

Oak has its own CI

This was the big one. At launch we were still building Oak on GitHub Actions. Now Oak has a native CI system: a control plane in the server that parses workflows out of .oak/workflows/*.yml, schedules runs and stores logs, plus a pluggable execution plane that today runs jobs in Cloudflare Sandboxes driven by a Durable Object.

On July 6 we moved oakspace’s own build and deploy off GitHub Actions and onto it. Oak’s CI is now tested by being the thing that ships Oak. In rough order, what it grew:

  • Per-repo secrets, encrypted at rest with ChaCha20-Poly1305, plus env: maps at workflow and job level.
  • Runs that can’t wedge. Dispatch is acked immediately and a RunCoordinator Durable Object drives jobs from an alarm loop, so a dropped request can’t leave a run stuck in “running” forever.
  • Live logs. An in-process event registry publishes every lifecycle moment — trigger, dispatch, each log chunk, conclusion, cancel, reaper — over SSE. No more refreshing to watch a build. ANSI escape codes render as color instead of [1m[92m noise.
  • Cancellation of in-flight runs, from the UI or the API.
  • Build caching. The sandbox image bakes in the pinned Rust toolchain, bun, nextest and zstd so per-run installs are no-ops, and workflows get a per-job cache: block backed by R2. Later we changed the cache upload to stream to R2 in parts rather than staging the whole thing on disk first.
  • Quotas per plan: metered minutes, plan-level concurrency with queueing, and run-duration caps, so one org can’t starve the fleet.
  • A merge gate. Squash merges are blocked on red CI. The block is surfaced on the branch slide-over instead of silently hiding the button, and there’s an explicit force path for when you mean it.
  • A CLI surface: oak ci runs, status, logs, rerun, and oak merge --wait, which rides out the gate instead of making an agent poll.

We also deleted the dedicated CI page. Run status now lives where the work is: a rolled-up status chip on every branch row and merged-commit row. A tab you have to remember to visit is a tab nobody visits.

One fun detour: multi-line CI secrets were arriving corrupted, which broke deploys with an inscrutable Load key: error in libcrypto. The cause turned out to be in Cloudflare’s Sandbox SDK, which indents every line of a multi-line env value when it serializes the environment — fine for PATH, fatal for an SSH private key. We worked around it and reported it upstream.

A feedback loop that agents can drive

There’s now an oak feedback command. It takes -m, a file, or stdin, opens $EDITOR when you give it nothing, works from a non-TTY, and returns a sequential reference — fb-N. Feedback storage started on Cloudflare D1 and moved onto the main Postgres a week later, with a Discord outbox, an admin triage view, an update API so an agent can triage its own reports, and a spam status for the inevitable.

This turned out to be the single most useful thing we built. Well over a hundred items have been filed, most of them by agents hitting friction mid-task, and a large share of the commits below are literally named after the number they close. It’s a very direct way to find out that oak reset . silently matches nothing, or that oak diff --print | head panics on a broken pipe.

Path permissions live in the repo

Repo-level access answers “who can see this repository.” Path permissions change that inside the tree, in both directions, and they’re declared in a file rather than in the platform — .oak/PERMISSIONS, CODEOWNERS-shaped, always read from the main tip:

infra/secrets/**   @zdgeier @group/sec-team
scripts/           @group/ops
scripts/public/    *
vault/

You can narrow a subtree to a chosen set of people while the rest of the repo stays open, or widen one with @public so a private repo can carry a genuinely open-source part. Grants can name org-level groups, not just individuals. The CLI knows about restricted content too, so a path you can’t read reads as restricted rather than as missing.

It started as database tables behind a feature flag and became a repo file, which is where it belonged. Still gated by the path-permissions flag while enforcement gets more mileage.

Files

Oak stores a lot more than code, so a chunk of late June went into making the web UI honest about non-text files. Previews and diffs now cover archives and packages, spreadsheets rendered as workbook grids, media containers with metadata diffs, 3D and CAD formats (VOX, X3D, 3MF, USD/USDZ, with semantic summaries for IFC and STEP), scientific binaries (GRIB, NumPy, FITS, miniSEED), fonts — including visual font diffs — markup, subtitles, calendars, contacts, CSS colors, and PostScript.

Audio got the most attention: bounded WAV/AIFF/CAF PCM decoding with waveform, peak and RMS rendering, before/after inline diff panels, and per-format playback help instead of a terse “your browser needs a decoder.” Underneath all of it is a set of bounded chunk readers and byte-range support, so previewing a 4 GB file doesn’t pull a 4 GB file.

Two more recent ones: diffs are syntax-highlighted and mark word-level changes inside modified lines, and you can drag files anywhere onto the home page to create a repo from them, or onto a repo’s file list to commit them.

Making hashes mean something

The least glamorous work and the most important. Oak commits are content-addressed, which is only worth anything if the hash is actually verified and the hashed fields actually can’t change.

So: stored commits’ hashed fields are now immutable and verifiable end to end, verification happens at insert in the storage layer rather than hopefully somewhere downstream, and a run of pull/clone bugs got root-caused instead of patched — wire serialization that couldn’t round-trip, backfill verifying a merge parent’s ancestors under the wrong branch name, Postgres timestamp truncation quietly changing a hash, and a pull that wedged forever after the branch was squash-merged remotely.

The honest part: branch rename was rewriting commits.branch_name — a hashed field — without re-hashing. Every renamed branch produced commits that fail verification on clone. We froze the rename endpoint behind a clear 503, wrote a full branch-identity redesign to fix the class of bug properly, reviewed it over six rounds, and then rejected it as not implementable. The design is checked in labelled REJECTED, because a dead end you can read is worth more than a dead end you rediscover.

Mount, review, and the CLI

oak mount --branch mounts an existing remote branch, and oak switch is mount-aware, so an agent handed a branch to review doesn’t have to materialize a checkout to look at it. Merge previews gained four-tree merge-safety detection: it distinguishes “this merges cleanly” from “this merges cleanly and also silently reverts work on the target.”

Smaller CLI things: shell completion, oak log -S and -G pickaxe search over diffs, an agent-first diff overhaul with progressive disclosure and conflict-aware evidence, content-similarity rename detection wired through every diff path, and oak skill install, which installs an Oak agent skill into your agent’s skill directory so it stops trying to run git status in an Oak repo.

Releases

Four releases — v0.99.0, v0.100.0, v0.101.0, v0.102.0 — and a rebuild of how release artifacts get published, done as three sequenced changesets so the readers landed before the writers: content-keyed reads, staged uploads through /api/releases/stage, content-addressed promotion, and unconditional retirement of the legacy release-write endpoint, with a release blackout and a fail-closed upload guard covering the migration window. Signing now derives its identity from the P12 itself, uses least-privilege tokens and persists no git credentials, and every commit gets CI coverage for the Windows and Intel-macOS targets.

Behind a flag: the software factory

There’s an orchestration layer in the tree behind a factory flag — design doc first, then implementation, then a web UI to configure it: connect an agent, set per-agent delivery secrets, edit path permissions and factory config as a proposed branch rather than as live mutable settings. It’s the answer to “what does a repo look like when most of the commits are written by agents you dispatched.” Not on yet. More on this when it is.

Everything else

  • Branch reopen, so closing a branch isn’t a one-way door.
  • Branch read receipts — who viewed a branch and when, so an agent that pushed work for review can tell “reviewed and ignored” from “nobody looked yet.”
  • Keyboard navigation for branch lists, file trees and the document pane.
  • A per-repo Ideas board, which can be public even on a private repo.
  • Security: a same-origin CSRF guard over every cookie-authenticated mutation, and private-repo existence leaks closed in the CI endpoints (404 where a 403 would have confirmed the repo exists).
  • Monitoring: growth graphs on the internal dashboard, five new Grafana alert rules including one that catches “box is up, app is dead,” and a fix for a metrics-cardinality overage that was 11k of 13k series in per-ASN counters nobody read.
  • Legal: counsel-reviewed AUP, Terms and Privacy Policy, with a DMCA designated agent, EU DSA-style illegal-content reporting, and governing law moved from Delaware to Washington.
  • Housekeeping: repo_pages.rs had reached 98,126 lines in a single file. It’s now 27 submodules, moved verbatim, with a script asserting the result tiles the original file exactly. And a steady drip of dark-mode contrast fixes, because dark mode is where unreadable text goes to hide.

What’s next

A checkout-free branch review API — read-only endpoints that give an agent a branch’s changed files, per-file hunks and a merge-safety verdict without materializing anything, with responses pinned to explicit heads so a moving branch can’t hand you an incoherent answer. The design landed last week; implementation is next. After that, branch identity take two, and getting the factory out from behind its flag.

Still no issues and no comments. Still bootstrapped entirely on Oak with no git backup, which remains the fastest way to find out what’s broken.

If you want to follow along or tell me what’s wrong with it, the Discord is the place — or file it straight from your terminal with oak feedback.

— Zach

Claim your username

get updates by email

Roughly monthly updates about development and cool things.