
Dogwalk — the CI/CD debugging spiral
Ever had one of those days where you spend more time debugging the CI than writing actual code? Yeah. Yesterday was that day for Dogwalk.
After weeks of quiet auto-sync commits, I decided to give the Dogwalk pipeline some love. The CI decided to fight back — classic “fix one thing, break another” spiral.
Diving into the YAML
First round was silly but tricky. The GitHub Actions workflow had a multi-line inline Python block:
- name: Run backend tests
run: |
python -c "
import asyncio
from app.database import init_db, settings
settings.db_url = '...'
asyncio.run(init_db())
"
python -m pytest app/tests/ -v --tb=short -x
The issue? GitHub Actions YAML doesn’t handle indentation inside python -c "..." the way you’d expect. The shell received broken lines at wrong positions, and Python complained about syntax. The fix was condensing everything into a single line with ;:
python3 -c "import asyncio; from app.database import init_db, settings; settings.db_url = '...'; asyncio.run(init_db())"
Less elegant, but it works 100% of the time.
Round 2: the phantom beautifulsoup
With the YAML fixed, the build ran — and died with ModuleNotFoundError: No module named 'bs4'. beautifulsoup4 simply wasn’t in requirements.txt.
It was probably a dependency I installed locally in the venv but never added to requirements.txt — a classic. Same thing happened with stripe, which was also missing.
Lesson learned: whenever you add a library, put it in requirements.txt right away. “I’ll add it later” becomes technical debt the first time CI runs.
Round 3: the test that needs a server
test_api_contract.py depends on a running server to test API contracts — and there’s no server in CI. pytest failed because it tried to connect to something that didn’t exist.
Simple solution: mark the test with @pytest.mark.skipif and skip it in CI. The test is still valid for local runs with a running server, but it no longer blocks the pipeline.
@pytest.mark.skipif(
os.environ.get("CI") == "true",
reason="Needs a running server"
)
def test_api_contract():
...
What stayed
Three fixes, zero feature changes — but the pipeline now runs clean. Best of all, every PR will go through CI before reaching production.
The bigger lesson: CI pipelines are like firewalls — you only remember they exist when they break. But when they work, you sleep peacefully knowing your build won’t break while you’re away.
In the end, Dogwalk went from “that project with a kinda broken CI” to “a project with a passing CI.” Small victory, but a victory nonetheless.