Field report · published 2026-08-26

112 days late

I spent 22 days building a macOS app to manage Claude Code sessions. Anthropic had shipped most of it 112 days before I wrote my first line — and I never checked. This is the full record, with dates, commit counts, and the audit that found out.

112days between their launch and my start
22days I worked on it
215commits
25,284lines of Swift
18/48features already replaced
15/48still not replaced

01The number that reframes everything

The story I told myself for three weeks was the familiar one: I was building something, and the platform vendor shipped it out from under me. It is a comfortable story. It casts you as unlucky rather than careless.

Then I actually checked the dates.

112

days between Anthropic shipping the thing and me starting to build it.

EventDateΔ from their launch
Anthropic ships the Claude Code desktop redesign — multi-session sidebar, per-session git worktrees, integrated terminal and file editor, split panes, preview pane. Routines ship the same day.2026-04-14day 0
Mobile push notifications land in v2.1.110 — including "push when actions required," for permission prompts2026-04-16+2
I create the first file in my project (a UI mockup, 19:56 local time)2026-08-04+112
I run my one and only competitive check before this audit2026-08-08+116
First git commit, 00:00:162026-08-09+117
I finally do a line-by-line audit and find out2026-08-24+132

The actual failure

Nobody overtook me. I started building a product that substantially existed, and I shipped 215 commits before anyone checked whether it did. That is not bad luck. That is a missing step in how I start projects — and it is a step that would have cost about forty minutes.

I am writing this down because the forty-minute version of this check is easy to describe and easy to reuse, and because the 22 days were not wasted — just not in the way I planned. Both halves are below, with the data.

02What I actually built

A native macOS menu-bar app. Three things it does: watch every Claude Code session on the machine, collect risky operations into one approval console, and host sessions directly in an embedded terminal.

Twelve delivery stages, all with written acceptance contracts. Here is the shipping record — dates are the day each stage was accepted, not the day work began.

StageWhat shippedAcceptedPR
2UI design frozen — 9-page information architecture, 5 visual mockups2026-08-04
1Technical spike — proved the two risky assumptions (three-channel external monitoring; SwiftTerm can host the Claude TUI, detection→UI measured at 14ms)2026-08-05
3Monitoring MVP — menu-bar icon, quick panel. New sessions appear in 2.0s (target was 10s)2026-08-05
4Main control center, read-only2026-08-05
5Embedded terminal host — PTY, tabs, split panes, birth registry, three injection automations2026-08-06
6Approval center + notifications — 10 risk predicates, decisions written into the hook response body2026-08-08
7Task triage and dispatch — headless CLI triage (25.8s: 3 tasks → 4 sessions), git worktree isolation2026-08-09
8Animated mascot — 7 states; plus the binary fingerprint gate that caught a real "you didn't swap the binary" on its first day2026-08-11
9Topic tracking — 8/8 criteria green, 3 runs at 100/100/100% precision, 89/94/94% recall2026-08-19#4
10Projects / History / browser pane / settings — 8 criteria, started and accepted the same day2026-08-19#5
XMulti-source progress tracking — 7 criteria2026-08-20#6
11Multi-agent abstraction — zero brand literals left in the core layer2026-08-21#7
12aCodex CLI read-only support — second agent provider, shipped same day it started2026-08-21#9
Y1Background execution base — 6 criteria, full regression 103 ✅ / 0 ❌2026-08-21#10
NPanel noise filtering + a reusable evidence-gate protocol2026-08-23#12
Y2aExecution-target advisor2026-08-24#12

Size, measured not estimated

MetricValueHow it was counted
Calendar days from first artifact to the audit202026-08-04 → 2026-08-24
Git commits215git rev-list --count HEAD, all in August 2026
Busiest single day61 commits2026-08-21 (stages 11, 12a and Y1 all landed)
Second busiest43 commits2026-08-23
Swift source76 files / 25,284 linesCore 10,957 · UI 9,435 · Providers 2,109 · Terminal 1,488
Design and process docs6,202 lines20 markdown files under docs/
Regression scripts12one per stage, each chaining all earlier ones
Evidence artifacts on disk530 fileslogs, screenshots, JSON snapshots under spike/out/
Pull requests12 (10 merged)#1 through #12; #11 and #12 still open

I mention the size not to impress anyone. I mention it because the point of this article is that all of it happened after the thing already existed, and volume of work is exactly what makes that failure invisible from the inside. Every one of those 22 days felt productive. Most of them were, locally. None of them were, strategically.

03The audit: 48 features, line by line

On 2026-08-24 I stopped building and did the check I should have done on day zero. I took the 48 features from my own internal feature matrix — the authoritative one, so I could not cherry-pick — and went through Anthropic's current documentation feature by feature.

Evidence rules, in order of weight: official documentation text first; release notes and reporting only to establish when something shipped; public issue-tracker status only to establish that something does not yet exist, which is stronger evidence than documentation silence.

18 already replaced 15 partial overlap 15 still unique 48 features from my own feature matrix, judged against official docs on 2026-08-24 Heaviest losses: embedded terminal · split panes · git worktree isolation · browser pane · execution-target picker context usage meter · session filtering · diff review · remote terminal · device pairing · mobile push Survivors that matter: cross-vendor abstraction · seeing terminal sessions · the approval console
Roughly a third replaced, a third overlapping, a third untouched. The split is mine, so treat it as an order of magnitude, not a measurement.

What the desktop redesign took

These are the ones that hurt, because they were my stages 5, 7 and 10 — the load-bearing ones:

  • Per-session git worktree isolation. Mine was stage 7. Theirs defaults to <project-root>/.claude/worktrees/, has a configurable location and branch prefix, a .worktreeinclude file for pulling gitignored files like .env into the worktree, and auto-archives when the PR merges. Theirs is more complete than mine.
  • Integrated terminal, file editor, split panes, drag-and-drop layout. My stage 5.
  • App preview pane and external site browsing. My stage 10, which I had not even started.
  • An execution-target picker. Local / Cloud / SSH / WSL, chosen when you start a session. I had decomposed this into five stages (Y1–Y5) and finished two of them. It is a dropdown they shipped in April.
  • A usage ring showing per-session context consumption and plan usage for the period. My context meter, plus a layer I did not have.
  • Session sidebar with filtering by status, project, and environment, and grouping by project.

What Remote Control took

Remote Control connects a local CLI session to claude.ai/code and the mobile apps. Two lines from the official documentation:

  • /config offers "Push when actions required" — for "permission prompts and questions."
  • "Claude Code keeps permission prompts and AskUserQuestion questions open until you answer them."

So approving a permission from your phone, and answering a multiple-choice prompt from your phone, are both free and official. Worth noting: I had explicitly declared remote answering of multiple-choice prompts a red line we will never cross. Anthropic just shipped it. On that specific point they were more aggressive than I was willing to be, which is its own kind of feedback about my product instincts.

What survived, and why

SurvivorWhy it survivedDurability
Cross-vendor abstraction — a core layer that knows no agent brand; Claude Code, Codex and opencode are all just providersAnthropic will never build an abstraction whose purpose is to also manage CodexStructural. Not a roadmap gap — a thing they cannot want
Seeing sessions started in a plain terminalOfficial docs are explicit: the desktop app sees only sessions it runs itself — not ones you started from the terminal CLI or the VS Code extension, "even in worktrees of the same project." There is an open feature request for itTime-limited. The day they unify the session store, this is gone
The approval console — one inbox across sessions, three risk tiers with impact text, an append-only decision log, per-event-type notification switchesOfficial permission modes grade autonomy (Manual / Accept edits / Plan / Auto / Bypass), not the risk of one request. And on notifications the docs say plainly: "Beyond the two on/off toggles below, there is no per-event configuration"Structural-ish. Their Auto mode is explicitly designed to reduce permission prompts. We are pointed in opposite directions

The distinction that took me three weeks to see

"Can you approve remotely?" — yes, officially, free, since April.
"Can you see all pending approvals in one place, ranked by risk, with a record of what you approved?" — not in any documentation I can find.

I had been selling the first sentence. Only the second one was ever true.

04How I missed it for 132 days

Here is the part I find genuinely useful, because the failure is specific and repeatable rather than "I should have researched more."

I did research. On 2026-08-08 — day 5 of the project — I ran a competitive check that was, by its own standards, rigorous. I pulled the official documentation rather than trusting secondhand write-ups. I found two research summaries that contradicted each other about whether you could approve permissions from a phone, went to the source, and established that you could — and that the summary claiming otherwise was stale. I enumerated eight hard limits of Remote Control. I deleted two of my own selling points on the spot because the evidence killed them.

The method was fine. The scope was drawn wrong.

The scoping error, stated precisely

The question I asked was: "Can Anthropic's mobile offering replace my mobile offering?"

So I checked Remote Control, Dispatch, Channels, scheduled tasks — everything on the phone line. Thoroughly.

The desktop redesign was not in that question's blast radius, because it is not a mobile feature. It had shipped 116 days earlier and it was the single biggest hit to my core product. I checked the line I was anxious about instead of the line they were shipping on.

The reusable form of the lesson:

  • Scope a competitive check by the opponent's product lines, not by the line you are worried about. Anxiety is a terrible index. Read their whole changelog, not the part that maps onto your fear.
  • Do it before the first commit, then on a schedule. Mine happened on day 5 and then never again for 16 days — during which I shipped nine stages. A recurring calendar entry would have caught it.
  • Write the result into the repo, not into a chat log. My August 8 findings did land in a spec file, which is why I could reconstruct exactly what I checked and what I did not. That part worked. Had it been a conversation, this article would have no evidence in it.

There is a smaller, more embarrassing corollary. Four months of platform releases changed the nature of at least 18 features in my product, and my project documentation contains no trace of any of it. I have 6,202 lines of design docs and a defect genealogy going back to day one, and not one line saying "the platform shipped X, here is what it means for us." I instrumented my code exhaustively and my market not at all.

05What the 22 days actually bought

Not a product. What I got instead was a set of engineering results with numbers attached, and those numbers turned out to be more transferable than the code.

The review economics

For stages 4 through 8 I ran multi-agent code review rounds — fan out reviewers across dimensions, then adversarially verify each finding. I kept the receipts.

StageReview scaleConfirmed findingsHit rate
418 agents (4 dimensions → adversarial verify)8
5 · round 138 agents (4 dimensions)29
5 · round 233 agents (reviewing round 1's fixes)20
6 · round 110 agents (5 dimensions)47
6 · round 27 agents12
7 · three rounds37 + 20 + 13 = 70 agents28+
8 · round 16 dimensions + per-finding adversarial verify14 (from 42 raised)33%
8 · round 2reviewing round 1's fixes6
Total~190 agents~170

Three findings fell out of that table that I did not expect:

1. Two thirds of what review agents raise is not real

Stage 8, round one: 42 findings raised, 14 survived adversarial verification. A 33% hit rate. Two thirds of the spend went into refuting problems that did not exist. The 28 that got knocked down were almost entirely pure code-reading inferences — "this looks like it would break."

2. The findings that mattered came from running, not reading

Both CRITICALs in that same stage came from execution, not analysis:

  • One had an actual crash report on disk — AgentCompanion-2026-08-10-131832.ips. A null inside a tool's input JSON reached dataWithJSONObject and aborted the process.
  • One was measured with ps: 20.3% CPU still burning after the panel was closed, against 0.5% with animation off. Nobody reads their way to that number.

Later I ran two entire stages — X and 11 — with zero review agents, and they still surfaced real crashes and real latent defects, because the work involved actually running the thing. By my count this pattern has now held five separate times in one project.

3. Reviewing the fix code always found a critical. Five stages, no exceptions

StageWorst thing found in round 1's own fixes
5The injection gate was reading the scrollback buffer instead of the live viewport bottom — fail-open. A keystroke delayed by 0.25s never re-checked the gate
6Two sides of resolveExternally used different keys, so "answer it in the terminal → the card disappears" was a dead path end to end
7Multiple confirmed, three of them majors introduced by round 1's own fixes
8A negative argument to a debug endpoint aborted the process; and the 20.3% idle CPU above

If you take one operational thing from this article, take this: a fix is not a smaller change than the thing it fixes. Review it like new code.

4. One module produced most of the criticals — and the root cause was undecidable

The injection gate — the component that decides whether it is safe to send a keystroke into a live terminal — produced more criticals than anything else in the project. Two in stage 5, two more in stage 5's second round (oscillating between fail-open and fail-closed), and in stage 7's third round it locked itself: the app blocked its own Enter key, permanently stalling three automation chains, with no dialog visible on screen to explain why.

The root cause is written into my own notes and it is not a bug: the gate is a lexical predicate. It matches strings to decide "is there a prompt on screen right now." That question is not decidable by string matching, so no number of review rounds fixes it — each round just pushes you to the next counterexample. Some defects are not defects. They are the shape of the design.

06Six incidents, dated

These are the ones I would tell a friend about. Each is in the project's defect genealogy with a date, because writing them down was a rule.

2026-08-20 — a symlink ate 823MB and 15 transcripts, permanently

A frozen test corpus, 823MB, gitignored, therefore not checked out into git worktrees. The fix was to symlink it from the main tree. The fix became the bomb: a .gitignore pattern written with a trailing slash matches directories and does not match symlinks, so the symlink got committed. After merging, a checkout in the main tree dropped a self-pointing symlink at that path and cleared the real corpus — git treats ignored files as expendable. 15 transcripts gone for good.

The tell was that the failure did not look like a missing fixture. It looked like a broken product: "181 work threads (ground truth 178) / 0 continuation chains / ai-title coverage 0 of 181." Absurd but specific numbers. The .gitignore now carries both the slashed and unslashed pattern, and the repair command is guarded by [ -e … ] || so it is a no-op in the main tree.

2026-08-20 — macOS quietly mangles non-ASCII environment variables

When the app relaunches itself from its bundle via NSWorkspace, non-ASCII environment variable values get reinterpreted as Latin-1. My project path contains Chinese characters, so 学习 arrived as å­¦ä¹. The isolation override silently failed and fell back to the real directory — while an ASCII-valued variable in the same launch worked perfectly. Test fixtures now live at ASCII-only paths, and the criterion asserts the override actually took effect.

2026-08-11 — the binary fingerprint gate caught a real one on day one

Every regression run starts by asking the running app for its binary fingerprint and refusing to continue if it does not match the build on disk. It caught a genuine "you did not actually swap the binary" the first time it ran. The failure mode it prevents is the nastiest kind: every subsequent test passes, against the old code, with no errors anywhere. The cause turned out to be mundane — an open terminal pane triggers a quit-confirmation dialog that silently blocks the kill, so the restart looks successful.

Amusingly, the first version of the fingerprint criterion was itself wrong: it ignored that codesign --force rewrites the binary in place, so the two sides were permanently 480 bytes apart.

2026-08-21 — another session swapped the binary mid-regression

One machine, one app instance, one port. Two Claude Code sessions working in parallel on the same repo, each with its own build. Whoever launches the app wins, and the other side's evidence is all invalid. Cost: a full regression run discarded halfway. The rule that came out of it is deliberately strict — handoffs count only on an explicit ping at both start and end. Reachability, uptime, fingerprints, "they've been quiet a while" — none of those count as a handoff.

Undated but recurring — I misjudged a running process as my own test fixture

I once identified a process as a leftover regression harness and killed it. It was the user's live work. The rule now: never kill, restart, or replace any running process without asking first, and by default assume every running process on the machine belongs to someone else.

2026-08-19 — the criterion that "passed" because it had no data

A screenshot criterion used screencapture without screen-recording permission. It captured nothing. The criterion counted "no data" as "no change" and printed a green check. This sits in a whole family I ended up cataloguing as fake evidence: verification paths that route around the mechanism they claim to test. An offscreen redraw that reported "correct" while the real render function was never called. An animation test where the window was occluded, the gate correctly closed, all 16 frames came out identical — and the criterion nearly filed correct behaviour as a fault.

07The rules I'm keeping

Seven things earned their place. I state them the way they are written in the repo, because the wording is load-bearing.

  1. Every new criterion must be made to fail on purpose first. Break the thing it tests, watch it go red, then fix it. An assertion never observed failing is not an assertion. This project logs its red proofs — stage N had 56 of them, stage Y2a had 54, all hitting.
  2. Static checks are not behavioural evidence. Learned expensively: a gate-detection layer in this codebase matched nothing at all for two entire stages while its static check stayed green. Every lexical predicate must be paired with a runtime case.
  3. When a test goes red, first decide: product defect or rotten fixture? Fixture rot is the normal case, not the exception — hardcoded search terms that expired, a regex where All|Bulk matched the "All" inside "Allow for This Session", a test binary path pointing into a directory that gets cleaned.
  4. One idempotent, sub-second evidence command per acceptance criterion, written when the stage starts. Retrofitted evidence is not evidence — you write it to pass.
  5. Resolve spec ambiguity at kickoff, in writing, in a comment at the top of the code, pinned by a test case. Not in a chat message. Chat messages do not survive.
  6. Overwrite the status document; never append. One file, under 80 lines, describing only right now: current stage, what was finished, what is next, what is unresolved. History goes to an archive and is never rewritten.
  7. Acceptance criteria are a contract. No measurable evidence means not done. No checking boxes you cannot prove. When something is missing, write down what is missing.

And the one I'd add after this article

Rule 8: date the platform. Before the first commit, write down what the platform vendor shipped in the last 6 months and what it means for each thing you plan to build. Then repeat it on a fixed schedule — not when you get nervous. Keep it in the repo next to the acceptance contracts, because it is an acceptance contract: it is the one that decides whether the rest are worth passing.

08What I'm doing with it

Not shutting it down. Repointing it.

The audit says the honest position is not "a control panel for Claude Code" — that is a race against a vendor who is inside the process, has all the information, and moves faster. It is "one approval console for every coding agent." That target sits in their structural blind spot: they will not manage Codex, ever.

The useful thing about that repositioning is that it needs no new features. The cross-vendor line was already underway — stage 11 emptied the core layer of brand literals, stage 12a added Codex as the second provider, stage 13 targets a third. What it needs is stopping four other things: the browser pane, the diff and task tabs for my own sessions, the mobile terminal mirror, and most of the execution-target work.

There is also a competitive fact I did not have before the audit: the cross-vendor menu-bar niche is no longer empty. There are several tools tracking Claude Code and Codex sessions from the menu bar now, one of which explicitly claims to cover CLI, Xcode, desktop app and Codex, and one of which is already charging for it. Every one of them stops at usage metering. Not one touches permission requests — because that requires hooking the permission channel, building a risk model, and handling timeout policy. The unoccupied square is the intersection, not either axis.

And a small one that matters more than it should: my dispatch feature has the same name as an official Anthropic feature that does an adjacent thing. That needs to change.

09Where this article is soft

An audit that does not say where it is weak is marketing.

  • "Not in the documentation" is not "does not exist." Most of the 15 survivors are judged on documentation silence. Vendors ship faster than they document. The approval-console claims are the most exposed to this.
  • I did not install the official desktop app and click through it. Every judgement about their capabilities comes from documentation text. The gap between "documented" and "actually good" is unmeasured in both directions.
  • The 18/15/15 split is mine. I chose the granularity of the 48 features and I drew the line between "partial overlap" and "replaced." Someone else would get different numbers. Treat it as an order of magnitude.
  • The review-economics numbers come from my own delivery notes, written at the time but by the person being measured. Stage 8's two rounds did not record per-round agent counts, which is why the total is "about 190."
  • The competitor claims are unverified. I read their marketing; I did not install them. If the one claiming to see CLI sessions actually does, the second-ranked moat is weaker than I said.
  • One thing I did not check at all: whether anybody wants any of this. Twenty-two days, zero users. The audit measures uniqueness, not demand — and 15 unique features that nobody has asked for is not obviously better than 18 replaced ones.

10Data appendix

Everything numeric in this article, with how it was obtained. All timestamps are local time, UTC+08:00.

Publication record

ArtifactCommittedCommitDeployment
The full feature-by-feature audit (Chinese)2026-08-24T21:58:29+08:00b84233dCloudflare Workers, version 3bf5f25d-5312-497c-bd46-6edc1cd8af03; 1 new asset uploaded, 360 unchanged; live checks returned 200 for both the article shell and the direct file; Supabase index gained 1 row
This article2026-08-26T12:19:54+08:00820c887Same pipeline: build → Cloudflare Workers → Supabase sync → push to prod

Project timeline, exact

TimestampEventSource
2026-04-14Claude Code desktop redesign ships; Routines enter research previewAnthropic announcement + press coverage
2026-04-16Mobile push notifications ship in v2.1.110release notes
2026-08-04 19:56First file in my project — docs/ui-mockups/vA.htmlfilesystem mtime
2026-08-04 20:07UI design spec writtenfilesystem mtime
2026-08-08My one pre-audit competitive checkdated section in the launch spec
2026-08-09 00:00:16First git commit — "Initial commit: Agent Companion for macOS"git log --reverse
2026-08-10 13:18:32Crash: null in tool input reaches dataWithJSONObjectAgentCompanion-2026-08-10-131832.ips, cited in the project retrospective
2026-08-17First pull request opened and merged (#1)GitHub
2026-08-20823MB frozen corpus destroyed; 15 transcripts lostdefect genealogy §2.5
2026-08-21Peak day — 61 commits; stages 11, 12a and Y1 all land (PRs #7, #9, #10)git log + GitHub
2026-08-24 17:36Last engineering commit before the auditgit log
2026-08-24 21:58Audit publishedsite repo

Commits per day

DateCommitsWhat was going on
2026-08-092repo initialised at midnight
2026-08-104stage 7 post-review fixes
2026-08-1114stage 8 accepted
2026-08-129stage 9 deviations logged
2026-08-145
2026-08-171PR #1
2026-08-1810stage X registered
2026-08-1928stages 9 and 10 both accepted, same day
2026-08-2013stage X accepted; corpus incident
2026-08-2161stages 11, 12a, Y1
2026-08-225stage N queue-jumped in
2026-08-2343stage N accepted; evidence-gate protocol built
2026-08-2420stage Y2a accepted; then the audit
Total21513 working days across a 16-day span

Verification volume

MeasureValue
Regression scripts (each chains all earlier stages)12
Evidence artifacts on disk530 files
Stage 9 acceptance8/8 criteria; 3 runs at 100/100/100% precision, 89/94/94% recall; regression 17 ✅ / 0 ❌ / 1 honestly skipped
Stage Y1 acceptance6 criteria; regression across 6 layers, 103 ✅ / 0 ❌ / 0 skipped
Stage N evidence gate7 evidence packs, 5 stamped, 56 red-proofs all hitting; 2 packs honestly left unstamped
Stage Y2a4 criteria stamped, 54 red-proofs
Regression state at time of writing21 pass / 2 fail on one chain, 7 pass / 1 fail on the other — both failures attributed, neither a product defect

Two of those rows are deliberately unflattering. A stage that reports "all green" while quietly skipping things is worth less than one that reports 5 of 7 and says which 2 and why. That was the rule, so the numbers here follow it too.

11Sources

Official documentation — the basis for every capability judgement

  • Desktop application — the session sidebar and its filters, per-session worktrees, split panes, usage ring, permission-mode table, tasks pane, cross-session messaging, cloud and SSH environments; and the passage stating that Claude does not see sessions started from the terminal CLI or the VS Code extension
  • Remote Control — answering permission prompts from a phone, the Push when actions required toggle, "there is no per-event configuration," where transcripts are stored, and the conditions that disable Remote Control entirely
  • Worktrees · Permission modes · Desktop scheduled tasks

Release dating

Public issue tracker — used only to establish absence

Companion piece

  • The full feature-by-feature audit (Chinese) — all 48 rows, the evidence for each judgement, and the roadmap consequences. This article is the story; that one is the worksheet.

Capability judgements reflect documentation publicly available on 2026-08-24. Anything shipped after that date is outside this article's scope. Project figures come from the repository itself — git history, file counts, and the delivery notes written at the time.