Flox auto-activation
Intro
I led and co-built cd-to-activate for Flox.
It shipped on by default in Flox 1.14.0.
That was about four months after a prototype I started in a day.
Flox environments only help when they're active.
Activating one meant typing flox activate.
Tools like direnv taught developers to expect activation on cd.
The bet was that people would use Flox more if it turned on by itself.
As a product designer at Flox, I kicked this off with a prototype of an idea on our CTO's roadmap. I wrote the user docs before the production code. I became project lead in mid-June, and I was a main implementer.
Team and collaboration
I worked most closely with our CTO and three engineers.
- Our CTO shaped the hook architecture.
- An engineer who was one of my main reviewers wrote our formal requirements and design. He also removed the feature flag.
- Another engineer worked on detach logic, shell fixes, and tests. That engineer approved more of my PRs than anyone else (16 of 28).
- A third engineer made
flox deactivatethe canonical command and shipped it without the flag. That engineer also built allow, deny, and environment restore.
Work moved from my prototype to user docs, then to formal requirements and small PRs.
Deactivation problem
It started as an experiment while I tried the Amp coding agent. What would a direnv-like Flox feel like?
Entering a directory turned out to be the easy half.
Leaving has to undo variables, [profile] scripts, services, and nested environments, in four shells.
That needs a flox deactivate that actually deactivates.
Ours was hidden and only told you to type exit.
A prototype in a day
Over March 19–20, I built the first version with Amp. Our CTO saw a working demo, not a mockup. The next two weeks of hardening were in Claude Code.
Docs as the spec
On March 26, I opened a user-facing docs PR. When the engineer who wrote our requirements noted the 4,000-line prototype was too big to review, I pitched it as a throwaway. The docs became the spec, which would force us to say how it works for an end user. Open questions went into a Notion doc.
After an April 2 design review, he turned the docs into formal requirements and a design. Then we rebuilt in small PRs.
I later wrote the shipped version of those docs, the auto-activation concept page:
Claude Code in production
Every commit in my 28 merged PRs carries a Claude co-author trailer. Claude's review bot caught a shell-injection risk in one of them before it merged.
The second time: making activation visible
In-place and auto-activations were silent, so you couldn't tell what was on. On July 30–31, I prototyped a fix with Claude Code. Our CTO pitched it as an experiment: prototype to release in one sprint.
Claude Code drafted six PRs. I questioned the telemetry one and had another one split, which made eight. Five merged on August 5, within three days of opening.
Two commands, not three
I cut flox hook, leaving flox activate and flox deactivate.
It's a simpler mental model, even though people who wanted only the hook now get a default environment.
One prompt per cd
I made the default one batched consent prompt, even though that costs an extra prompt.
While it was behind a flag, anyone who turned it on wanted to try it.
So requiring flox activate allow first felt like needless friction.
The docs show the prompt:
Auto-activate the environment in '/path/to/project'? [y/N] Before launch, the engineer who wrote our requirements made a "no" stick, since typing a command to refuse for good was surprising. I agreed.
Announce once, then get out of the way
Colleagues suggested announcing activations in one line and showing default environments in the prompt.
My framing was that a line on each change buys a shorter prompt.
So flox [acme/core (local)] became flox [core].
We also considered abbreviating the owner instead of hiding it, and adding a setting to silence announcements.
I decided against both.
You know whose environment you're in, and the old (bad) behavior didn't need a setting.
The cost is that two default environments look alike, and announcements have no off setting.
What didn't work
- The prototype was good for feel and poor for handoff.
It skipped
[profile]scripts, which the engineer who wrote our requirements warned might be the hardest part. He guessed the prototype took a week and predicted ten times that for production. I'd spent two and a half weeks, and production took about 13 more weeks (closer to five times). My second prototype was under 500 lines and shipped as five small PRs (+164 lines), though for a smaller feature. - I underestimated deactivation. It turned out to be most of the work.
- The AI was confidently wrong.
The engineer who wrote our requirements spotted odd behavior in fish.
I traced it to what looked like Claude misreading direnv's docs.
Claude also kept writing unsafe tcsh, so I added a rule to the repo's
AGENTS.md. - The telemetry that would have counted auto-activations didn't ship, so I can't report adoption.
Outcome
flox deactivateshipped in 1.13.0 (June 17). Auto-activation went GA in 1.14.0 (July 29) with a consent prompt.- The visibility changes shipped in 1.14.1 (August 11), about 12 days after their prototype. That spanned two of our one-week sprints, not one.
- I diagnosed a 1.18.0 deactivation regression with Claude. The engineer who approved most of my PRs and I agreed to hotfix it. The fix merged 58 minutes after I opened it and shipped in 1.18.1 the next day.
- I authored 28 merged PRs (+7,315/-1,128) and the docs concept page. A colleague wrote the launch post.