Octofs 0.16: A State-of-the-Art Filesystem for Claude Code and Codex
Reading a 400-line file takes microseconds. Deciding to read it again takes a model turn, and a model turn takes seconds. That asymmetry is the whole story of agent speed: the disk is never the bottleneck. The bottleneck is how many times the model has to stop, look, and ask again before it can act.
For the last four weeks we ran octofs as the only filesystem and shell under Claude Code and Codex. We switched off the built-in file and shell tools and fixed whatever got in the way. Eight releases later, 0.15.0 through 0.16.0, this is what we measured: the same tasks finish 2–2.5x faster, at roughly the same token count. Same model, about the same tokens, less time.
Faster than the tools the clients ship with, at no extra token cost. That's the bar we set for calling a filesystem state of the art, and 0.16 clears it. This post is the release notes for those eight releases, plus the exact config to try it yourself.
Where the time goes
Octofs doesn't make the model think faster. It removes the turns where the model was only re-establishing what it already knew. Each of these is in the tool contract the model sees:
- Edits return what changed, with fresh ids. Every line octofs shows has an id: its line number plus a short hash of its content, rendered as
N:hh|content.batch_editandtext_editoranswer with a diff whose lines carry new ids, so the next edit targets them directly, without a re-read in between. 0.15.0 added a trailingshift:line that says how line numbers below each edit moved, so ids the model still holds from an earlier read stay usable too. - A re-read returns only the difference. Since 0.15.0, viewing a whole file the session has already seen returns only the hunks that changed, or a one-line "unchanged" marker. Octofs' own edits keep that cache current, so re-checking your own edit costs one line.
- A wrong target fails with the answer attached. A stale line id doesn't just fail. The error carries the current content and where the target moved, so the retry happens without another read. That's been true since 0.9.0.
- Long commands don't hold the turn. A command still running at ten seconds becomes a background job. It's the same process, not killed or restarted, and the model gets its turn back. That arrived in 0.13 and 0.14.
- One response, many calls. The server instructions tell the model outright that each response costs a round trip, so it should request every independent call at once. 0.16.0 sharpened that wording.
We haven't broken the 2–2.5x down by mechanism, so read the list as the design, not as an attribution. None of these ideas is new in this release. What four weeks under real clients added is the polish that keeps the model on the fast path instead of letting it fall off.
What changed in 0.15 and 0.16
Claude Code sees the tools again
This is the fix to know about if you tried octofs in a recent Claude Code and got nothing. The 2026-07-28 MCP revision makes cache hints (ttlMs and cacheScope) required on list and read results. Claude Code's strict client for that revision rejected results without them, so it loaded no octofs tools at all. 0.16.0 emits the hints whenever the client speaks 2026-07-28 and leaves older connections unchanged.
Background jobs you can read with view
A background job is exposed as a link, octofs://jobs/<id>. Models reach for view on that link first, and Claude Code keeps its generic resource reader behind tool search, which costs an extra round trip. So view on a job link returns the job's status and output tail. As of 0.16.0 it returns immediately instead of waiting for the job to exit. That's the one breaking change in this cycle, and the reason for it: a model can check on a build mid-run and keep working.
Claude Code also doesn't show the model the standard resources/updated notification, so a job's exit doesn't wake the session. The model picks up the result by viewing the link when it needs it, which is why that read must never block.
One more guard in 0.16.0: when a command that stashes changes (git stash) moves to the background, the response warns the model that the working tree is missing those changes until the command exits, so it doesn't edit files or call the task done in the meantime.
Edits that leave files exactly as they were
- Line endings survive. CRLF files keep CRLF through replacements and insertions (0.15.5).
- Final blank lines survive, and an empty insert means a blank line instead of nothing (0.16.0).
- Ambiguous operations are rejected, not guessed. An insert inside a range the same batch replaces or deletes now fails with an explanation (0.15.0, 0.15.2).
- Long diffs stay readable. In the edit result, the middle of a long exact replacement or a long added block collapses to an id range (0.16.0).
A shell that keeps the model on the dedicated tools
Octofs rejects shell commands that duplicate a dedicated tool, like cat, grep or ls, because the dedicated tools return line ids and raw shell output doesn't. This cycle made that gate precise:
- Blocked reads are caught wherever they start a command: alone, chained with
&∨, inside$(…), or at the head of a pipeline (0.16.0). A later pipe stage likecargo test 2>&1 | tail -20stays allowed. - Read-only
sedandawkare allowed (0.15.6). Streaming text to stdout isn't something a dedicated tool covers. In-placesed -iis still rejected with a pointer to the edit tools. - Redirects are parsed (0.16.0). Octofs rejects writing file content into the project with
echo,printforcat, since quoting corrupts content. Redirecting a program's output, likecargo test > out.log, is allowed. - Every rejection names the blocked program and says nothing ran, so the model splits a compound command instead of retrying it whole (0.15.6).
Remote hosts and protocol
ssh://hostandssh://host/~/dirresolve against the login user's home, likessh hostdoes (0.15.0).- Content search over remote trees was fixed, and a bare remote listing defaults to one level deep, because every subdirectory costs an SFTP round trip (0.15.1). The SFTP layer moved to
russh-sftp3.0 (0.16.0). - Published tool schemas no longer carry generator noise like
$schema, integerformattags anddefault: null(0.16.0). Tool definitions are sent with every request, so they should say only what the model acts on.
Swap it in: Claude Code
1. Install octofs (Homebrew, Cargo or npm):
brew install muvon/tap/octofs
# or: cargo install octofs
# or: npm install -g @muvon/octofs
2. Register it for every project:
claude mcp add --scope user octofs -- octofs mcp
Claude Code starts the server in the directory you launched claude from, and that directory becomes octofs' project root.
3. Turn off the built-in editors in ~/.claude/settings.json:
{
"permissions": {
"allow": ["mcp__octofs"],
"deny": ["Edit", "Write", "NotebookEdit"]
}
}
This is the setup behind our numbers. A bare tool name in deny removes the tool from the model's context entirely, and mcp__octofs allows every octofs tool without a prompt. Bash and Read stay: in our runs the model sent its reads, edits and commands through octofs anyway, and Read is how Claude Code looks at images and PDFs. Denying Bash as well backfires, because it brings Claude Code's standalone Glob and Grep tools back into the model's toolset.
Swap it in: Codex
1. Add octofs and turn off the built-in shell in ~/.codex/config.toml:
[features]
shell_tool = false
unified_exec = false
[mcp_servers.octofs]
command = "octofs"
args = ["mcp"]
default_tools_approval_mode = "approve"
Paste the block as is, and don't also run codex mcp add octofs: it writes the same [mcp_servers.octofs] table, and Codex refuses to start with a table defined twice. shell_tool and unified_exec are Codex's two command runners. With both off, every command goes through octofs' shell, background promotion included, and default_tools_approval_mode lets octofs tools run without an approval prompt.
2. Point edits at octofs. Codex has no switch to turn off its built-in apply_patch editor (openai/codex#8161 was closed as not planned), so tell the model in AGENTS.md:
## Tools
- Use octofs for all file and shell work: `view` to read, list and search; `batch_edit` or `text_editor` to edit; `shell` for builds, tests and git. Never use apply_patch.
- Put every call that doesn't depend on another call's result in one response.
- A command still running after ~10s becomes a background job. Don't wait for it: take the next step, and `view` its `octofs://jobs/<id>` link when you need the result.
The same block works in CLAUDE.md, though Claude Code picked octofs without it in our runs.
Try it
The quickest way to check our number is to rerun something you've already done. Pick a recent task, like a bug fix or a refactor that touches a handful of files. Run it once with the built-in tools and once with octofs, same model and same prompt, and compare wall-clock time and tokens. Expect the tokens to land in the same place. The time is where the difference shows.
Already on octofs? Upgrade with brew upgrade muvon/tap/octofs, cargo install octofs or npm install -g @muvon/octofs, or grab a pre-built binary for Linux, macOS or Windows (x86_64 and ARM64) from the releases page. There's one behavioral note: view on a background-job link no longer blocks until the job exits. If something you built relied on that, read the link again once the job finishes.
Octofs is open source (Apache 2.0) at github.com/Muvon/octofs. Introducing Octofs covered why we built our own filesystem server, 0.9.0 covered why a line number can't be trusted, and 0.14 covered why a blocked tool call can't be either. This one is the payoff of all three: an agent that stops re-checking what it already knows finishes the same work two to two and a half times faster.



