I started the day staring at a pg_roles.rolconfig query result and ended it staring at a Hugo binary size chart. In between - a production release, an open-source launch, and the uncomfortable discovery that last week’s disaster recovery missed 17 files.
The morning began with Production Release #182. Forty-one tickets merged from preview to main. The /production workflow I’d rebuilt after the messy February 14 deploy ran cleanly this time - the changelog CLI found its Supabase env vars, the merge sync pattern (git merge -s ours origin/main) prevented the ancestry divergence that bit us last time, and CI passed on first try.
I shipped it and moved on. That’s the goal with production releases - they should be boring.
The CEOS repo went public today. Version 1.0.0.
CEOS is an EOS (Entrepreneurial Operating System) implementation built entirely as Claude Code skills. Seventeen skills that cover the full EOS toolbox - from V/TO and Rocks to Scorecards and L10 meetings. All file-based, no database, designed to run in any directory with a .ceos config file.
Getting to 1.0.0 meant a chain of cleanup tickets. I used the new /start chain command - which I’d just built the day before as PLA-526 - to process ten CEOS tickets autonomously. Update the GitHub issue templates with all 17 skills (they still listed five). Add data format specs for the eight newer data types. Extract the Skill Structure Contract from CLAUDE.md into publishable docs. Delete four stale feature branches. Fix the skill-authoring guide that still called three shipped skills “future.”
The chain mode worked. Each ticket got its own plan, its own implementation, its own commit. I watched it churn through them while reviewing the magic-platform staging queue.
Two new skills landed as part of the chain: ceos-lma (Leadership + Management = Accountability - tool #11 in the EOS toolbox) and a 5-5-5 quick mode for ceos-quarterly (the 15-minute version of the Quarterly Conversation).
The Hugo binary problem was the interesting puzzle of the day.
@platform/sites has a buildSite() function that shells out to the hugo CLI via child_process.execFile. Works perfectly in local dev where Hugo is installed globally. Doesn’t work at all on Vercel, where serverless functions don’t include random Go binaries.
I evaluated four options:
- Bundle the binary in the function - download Hugo during build, include it in the deployment
- GitHub Actions pipeline - run Hugo in CI, upload artifacts
- Vercel’s built-in Hugo support - use their framework detection
- Wasm Hugo - compile Hugo to WebAssembly
Options 3 and 4 died quickly. Vercel’s Hugo support assumes Hugo is your primary build tool - we’re using it as a library called from Next.js server actions. And there’s no official Hugo Wasm build.
Option 2 adds CI complexity for no real benefit. The question was whether Option 1 would blow the size budget.
Hugo standard (linux-amd64) is 49 MB uncompressed. Vercel allows 250 MB per serverless function. That’s 20% of the budget. With Next.js runtime overhead at 30-50 MB typical, we’d still have 150-170 MB of headroom.
The implementation is a three-tier binary resolution in getHugoBinaryPath(): check HUGO_BINARY_PATH env var first (explicit override), then look for a bundled bin/hugo relative to process.cwd() (downloaded during Vercel build), then fall back to "hugo" on PATH (local dev). A scripts/download-hugo.sh handles the download with SHA256 verification for pinned versions.
(Skip the next two paragraphs if you don’t care about Vitest mocking patterns.)
Testing this required mocking fs.existsSync - but Vitest with ESM modules freezes namespace objects. vi.spyOn(fs, 'existsSync') throws “Cannot spy on export. Module namespace is not configurable in ESM.” The fix is vi.hoisted() to create a mock reference that gets hoisted above imports, combined with vi.mock("node:fs") to replace the entire module factory. This is the canonical pattern for mocking Node built-ins in Vitest ESM mode, and it’s not obvious from the docs.
The test review also caught me hardcoding expect(buildCall[0]).toBe("hugo") instead of expect(buildCall[0]).toBe(getHugoBinaryPath()). Subtle - the test would pass in isolation but could fail if HUGO_BINARY_PATH leaked from another test. Environment contamination bugs are the worst kind of test flake.
The discovery that rattled me was about last week’s disaster recovery.
On February 14, magic0’s .claude/ directories got destroyed by self-referencing symlinks. We recovered and I built a guard script with three protection layers. The ticket said we’d recovered environment-health-auditor.md and review-battery.md.
Except today I did a full inventory comparison against the magic3 backup. Seven missing agents. Eight missing commands. Two missing docs. The backup was the only surviving copy.
The guard script I reviewed today is solid - source-exclusion blocks magic0 as target, source validation ensures directories are real, data protection backs up before replacing. But the real lesson isn’t about prevention. It’s about verification.
After any disaster recovery, do a full inventory comparison against every available backup before declaring it complete. A partial recovery that seems “done” is more dangerous than an obvious failure. You stop looking. The missing files only surface weeks later when a workflow silently breaks.
I added automated daily backups via launchd and a manifest-based --check command to catch this going forward.
The unified ticket pipeline (PLA-525) also shipped to production today. This was a multi-phase project that replaced five project-specific /commit files (1,200+ lines) with a single profile-driven command. Each project declares its workflow policy in a YAML block in CLAUDE.md - base branch, quality gates, review level, ship method, Linear status. The command reads the profile and adapts.
Chain mode (PLA-526) was the capstone - /start chain TICKET-1 TICKET-2 processes tickets autonomously. Plans are auto-approved, user testing is skipped, commits happen between tickets. The CEOS chain was its first real-world use.
Adventures in Claude got social media auto-posting. New blog posts now automatically tweet and post to LinkedIn after git push. Both integrations use OAuth 2.0 and degrade gracefully if the API call fails.
Eighty-something commits across five repositories. Twenty tickets marked Done. One production release. One open-source v1.0.0. And the humbling reminder that verifying recovery is harder than performing it.
Tomorrow I’ll stage the Hugo binary work and the remaining AuthorMagic fixes. The AuthorMagic web site builder is getting close to being finished.