A real-world case study in why continuous integration is essential for catching environment-specific issues
The Problem: “It Works on My Machine”
We’ve all been there. Your code runs perfectly in your local development environment, all tests pass, and everything seems ready for production. But then your CI pipeline fails with a cryptic error that makes no sense given your local success.
This exact scenario happened to us recently while working on the Internet Directory project, and it perfectly illustrates why robust CI/CD pipelines are invaluable for catching issues that local development simply cannot detect.
The Mysterious Jenkins Failure
Our Jenkins CI pipeline started failing with this error:
ModuleNotFoundError: No module named 'app.models.enums'
The confusing part? Everything worked perfectly locally:
- ✅ All imports succeeded
- ✅ Tests passed
- ✅ Application ran without issues
- ✅ The file
app/models/enums.py
clearly existed
The Investigation
When faced with a “works locally, fails in CI” situation, the first instinct is often to blame the CI environment. But Jenkins was actually doing us a huge favor by exposing a critical configuration issue.
Here’s what we discovered through systematic debugging:
Step 1: Reproducing the Issue Locally
# Test the exact import that was failing in Jenkins
python -c "from app.models.enums import SiteSource"
# ✅ Success locally
# Check if file exists
ls app/models/enums.py
# ✅ File exists
# Check git status
git status
# ✅ Working tree clean
Everything looked normal, which made the Jenkins failure even more puzzling.
Step 2: The Git Discovery
The breakthrough came when we checked what files were actually tracked by git:
git ls-files | grep enums
# ❌ No output - file not tracked!
Despite the file existing locally and git status
showing a clean working tree, the critical enums.py
file was never committed to the repository.
Step 3: The Root Cause
The culprit was hiding in our .gitignore
file:
# Model files and checkpoints
models/
This innocent-looking line was designed to ignore machine learning model files, but it had an unintended consequence: it was also ignoring our SQLAlchemy model directory at backend/app/models/
.
Why Jenkins Caught This But Local Testing Didn’t
This is a perfect example of why CI environments are so valuable:
Local Environment Characteristics:
- Persistent state: Files created during development stay around
- Incremental changes: You rarely start from a completely clean slate
- Developer assumptions: You know what files “should” be there
CI Environment Characteristics:
- Fresh checkout: Every build starts with a clean git clone
- Only committed files: If it’s not in git, it doesn’t exist
- No assumptions: The environment only knows what’s explicitly defined
Jenkins was essentially performing a clean room test that revealed our git configuration was broken.
The Broader Implications
This incident highlighted several critical issues that could have caused problems in production:
- Deployment Failures: Production deployments would have failed with the same missing file error
- Team Collaboration Issues: New team members cloning the repository would be unable to run the application
- Backup/Recovery Problems: Disaster recovery procedures would fail due to missing critical files
The Fix and Lessons Learned
Immediate Fix:
# Fix the .gitignore to be more specific
- models/
+ /models/
+ backend/ml_models/
# Add the missing files
git add backend/app/models/enums.py
git add backend/app/models/pending_domain.py
git commit -m "Fix missing model files"
Long-term Lessons:
- CI is Your Safety Net: Never skip CI checks, even for “simple” changes
- Test Fresh Environments: Regularly test in clean environments that mirror your CI
- Be Specific with .gitignore: Overly broad patterns can cause unexpected issues
- Trust Your CI: When CI fails but local works, investigate thoroughly rather than assuming CI is wrong
Creating a Local Jenkins Simulation
To prevent this in the future, we created a simple test script that simulates the Jenkins environment:
#!/bin/bash
# Clear Python cache to simulate fresh environment
find . -name "__pycache__" -type d -exec rm -rf {} +
# Test imports in a fresh Python session
python -c "
import sys
sys.path.insert(0, '.')
from app.models.enums import SiteSource
print('✅ Import successful')
"
This allows developers to catch git configuration issues before they reach CI.
Conclusion
This incident perfectly demonstrates why continuous integration is not just about running tests—it’s about validating that your entire development workflow is sound. Jenkins didn’t just catch a bug; it caught a process failure that could have caused significant problems down the line.
The next time your CI fails with a mysterious error while everything works locally, don’t dismiss it as a “CI problem.” Instead, treat it as valuable feedback about potential issues in your development process, git configuration, or environment assumptions.
Your CI pipeline is often the first line of defense against the dreaded “it works on my machine” syndrome. Trust it, investigate thoroughly, and you’ll often discover issues that would have been much more expensive to fix in production.
Have you experienced similar “works locally, fails in CI” situations? What did they teach you about your development process? Share your stories in the comments below.