How devenv Fixed My AI Coding Workflow
I use Claude Code as my primary coding partner. It writes features, fixes bugs, runs tests, reads the output, and iterates. The tighter that feedback loop, the faster I ship. But for weeks, a fragmented dev setup was quietly slowing everything down.
The Problem: Death by a Thousand Scripts
My test setup had grown organically. devenv handled PostgreSQL — and nothing else. Everything on top was bash scripts: start the API, run migrations, seed the database, boot the frontends. The scripts worked, mostly. But they were opaque.
The real issue wasn't that things broke. It was that the configuration was scattered across multiple files with implicit dependencies. Which script starts what? In which order? What needs to be running before the E2E tests can start? I knew the answers because I wrote the scripts. Claude Code didn't.
When an AI agent can't understand your dev setup, it can't iterate autonomously. It would start tests before the API was ready, skip migrations, or fail to reset the database between runs. Every failed attempt cost time — not just the test run, but the back-and-forth to diagnose what went wrong.
The Fix: One File, One Command
I consolidated everything into a single devenv.nix. Database, build, migrations, seed data, API server, frontend dev servers — all declared in one place with explicit dependencies.
The key is process-compose integration. Each process declares what it depends on:
backend-prepare = {
# Wait for PostgreSQL, build JAR, run migrations, seed DB
process-compose.depends_on.postgres.condition = "process_healthy";
availability.restart = "no";
};
backend = {
# Start the API server
process-compose.depends_on.backend-prepare.condition = "process_completed_successfully";
};In test mode, backend-prepare automatically resets the database before doing anything else — drop schema, recreate, migrate, seed. No stale state between runs.
The test entry point waits for all required ports to be available before running Playwright:
enterTest = ''
wait_for_port 20000 # API
wait_for_port 3005 # Management frontend
wait_for_port 3001 # PWA
npx playwright test
'';The entire E2E cycle is now a single command: devenv test. It starts everything, waits until the stack is healthy, runs the tests, and reports the results.
Why This Matters for AI-Assisted Development
A good iteration loop for Claude Code rests on three pillars:
A declarative environment. A .nix file is readable. When Claude Code needs to understand how the stack works, it reads one file and knows everything — ports, dependencies, startup order, environment variables. No chasing through a maze of shell scripts.
Structured output. devenv test produces predictable output. Tests pass or fail with clear error messages. The agent reads the output, understands what broke, and tries a fix. No guessing whether the failure was a real test failure or a setup problem.
End-to-end tests as the feedback signal. Unit tests are fast but narrow. E2E tests tell the agent whether the feature actually works from a user's perspective. Combined with a reliable test environment, they become the agent's primary feedback mechanism.
The result: Claude Code runs devenv test, reads the Playwright output, adjusts the code, and runs it again. The loop that used to involve manual intervention now runs autonomously.
Takeaways
- Declarative beats imperative — especially when an AI agent needs to understand your setup. A
.nixfile is documentation and configuration in one. - One command, predictable output. If your test workflow requires tribal knowledge, an agent can't use it.
- Investing in dev tooling pays off twice — once for you, once for every AI agent that works in your codebase.
The irony is that everything I did to make my setup work better for Claude Code also made it better for me. Turns out, what's legible for an AI is legible for a human coming back to the project after two weeks off.