Approach · workflow · in production
CI Local Before CI Remote
I run all CI checks and validations locally before pushing code, ensuring immediate feedback and preventing remote build failures.
CI Local Before CI Remote means I run the entire suite of continuous integration checks—linting, formatting, type checking, and all tests—on my development machine before committing or pushing code to a remote repository. This shifts the feedback loop left, catching issues in seconds rather than waiting for a remote pipeline to fail minutes later.
What it is
This approach involves configuring local development environments to execute the same validation steps that a remote CI/CD pipeline would. It's not just about basic linting; it encompasses the full battery of checks: code formatting, static analysis, type validation, unit tests, and often a subset of integration tests. The goal is to replicate the remote CI environment's validation stage as closely as possible on my machine, ensuring that any code I push is already verified to pass the foundational checks.
Why I do it this way
The primary driver is efficiency. Waiting for a remote CI pipeline—whether it's GitHub Actions, Vercel, or a custom Firebase deployment script—to run for several minutes only to report a trivial linting error or type mismatch is a waste of time and resources. By front-loading these checks, I eliminate unnecessary remote build cycles. This aligns with my Fail Fast, Fix Faster philosophy, allowing me to identify and correct issues immediately, often before the code even leaves my editor. For a solo operator running a multi-product studio with AI as the team, this efficiency is critical. It reduces the cognitive load of context switching and keeps my focus on building. Furthermore, it's a pragmatic approach that contributes to Cash Before Vanity by minimizing compute time on remote CI services, which can accrue costs.
How it works in practice
I implement this using `git hooks`, primarily `pre-commit` and `pre-push` hooks managed by `husky`. For monorepos, `nx` is indispensable. When working on Total Formula 1, for instance, `pre-push` hooks trigger `nx affected:lint`, `nx affected:typecheck`, and `nx affected:test`. This ensures only the projects impacted by my changes are validated, keeping the local feedback loop fast. If any check fails, the push is aborted, preventing a broken state from ever reaching the remote. For Firebase functions, I'll often spin up `firebase emulators:start` and run local integration tests against them before deployment. For frontends like Pregnancy Power Hour or Inky, which deploy to Vercel, the local checks mirror Vercel's build process. This includes running `eslint`, `prettier`, and `tsc` for type safety, which I consider part of Tests as Design—ensuring the code's structure and behavior are sound from the outset. My agentic engineering workflow, where Claude Code or Gemini might generate code, heavily relies on these local checks to validate agent output before I accept and commit it.
Where this breaks down
This approach is highly effective, but it has limitations. The most common issue is environment drift: if the local development environment isn't perfectly aligned with the remote CI environment (e.g., different Node.js versions, OS differences), a local pass might still result in a remote failure. I mitigate this with strict version pinning and using tools like `nvm` or Docker. Another challenge arises with complex integration tests that require external, non-mockable services or specific network configurations not easily replicated locally. In these cases, a remote CI stage is still necessary for the final validation. Lastly, if the local validation suite becomes too time-consuming, there's a risk of developers bypassing the hooks. My use of `nx affected` helps keep local runtimes manageable, but it's a constant balance between thoroughness and developer experience.
FAQs
- Does this replace remote CI pipelines?
- No. It front-loads the majority of checks. Remote CI still handles deployment, final integration tests requiring external services, and security scans.
- What if local checks take too long?
- For monorepos, use tools like `nx affected` to run checks only on changed projects. For smaller repos, ensure your test suite is optimized for speed.
- How do you ensure local and remote environments match?
- I use strict version pinning for dependencies and Node.js. Tools like `nvm` or Docker containers help maintain consistency across environments.
I run this and four other brands. Want to see the operator playbook in detail?
Get the Builder's Playbook →