I run a solo health tech product. No QA team, no budget for one. Every time I shipped something, something else quietly broke somewhere else: a login flow that started 404ing, a pricing page showing NaN, an affiliate link that silently stopped tracking. I usually found out from a customer, or never.
So I built Muraqib (مراقب, Arabic for "the watcher"). Every night at 2am it runs Playwright tests against my live app. If something fails, it opens a GitHub issue, Claude reads the failure, figures out whether the test is stale or the code is actually broken, and opens a PR with a fix. I read it for 30 seconds, merge, done. Once a week I get a summary email. The rest of the time I hear nothing, unless something is genuinely on fire.
Cost: $0/month on top of a Claude subscription I already had. GitHub Actions is free for public repos, Resend's free tier covers the emails.
I wasn't first with this idea. Octomind built something similar as a paid product starting at $89/month, and stopped taking new customers this April. What I couldn't find anywhere was a free, self-hostable, fully open version of the whole chain (nightly run, auto issue, AI diagnosis, PR) that you could set up in an afternoon. The individual pieces all exist separately. So I put them together and open sourced it.
Before I told anyone about it, I decided to attack it myself first. That turned out to be the actual interesting part.
Night one: the three bugs I found in my own first commit
A script injection in the workflow itself. Failure text and workflow inputs were spliced directly into a JS template literal and a bash string using ${{ }}, instead of going through an env: variable. That is a textbook GitHub Actions script injection (CWE-94): a backtick or ${...} in the wrong place and someone can run arbitrary code with the workflow's write token. Fixed by routing everything through env: and reading it back with process.env.
The YAML had been structurally invalid since the very first commit. A multi-line JS template literal was written without indentation inside a YAML block scalar. That breaks the surrounding YAML. I didn't catch this by reading the file, I caught it by actually calling the real workflow_dispatch API and reading GitHub's own parse error. Which means the auto-fix engine, the actual core feature of this tool, had probably never once run correctly, in any of the three projects I'd copied it into, since day one. A YAML-valid file is not the same thing as a GitHub-Actions-valid file. If you're not sure, trigger it for real.
Auto-merge with no boundaries. By default it would merge anything once tests passed, no distinction between "fixed a stale selector" and "touched the payment flow." Turned off by default now, and even when you opt in, payment, auth, and migration changes always wait for a human, no exceptions.
None of this shipped to any user before I found it. That's not luck, it's what happens when you go looking on purpose instead of assuming your own code is fine because you wrote it carefully.
Building a second line of defense, and breaking it three times
Telling an AI "don't touch payment code" is a prompt, not a guarantee. So I built a second, code based check: a GitHub Action that inspects the actual changed files on any PR with auto-merge enabled, and force-disables it if a sensitive path is touched. Before I let it near anything real, I ran it through dedicated adversarial review, not a casual read-through, an explicit "try to break this" pass.
Round one: broken. It used pull_request instead of pull_request_target, which means GitHub reads the workflow's own definition from the PR branch itself. A PR that weakens the guard and touches a sensitive file in the same diff would be checked against its own already neutered copy of the rules. The tests also used a hand-copied JS RegExp while production used grep -E, two different regex dialects that don't always agree, so green tests didn't prove anything about the real code path.
Round two, after a full rebuild: a new, completely different bug. gh api --paginate --jq silently returns multiple concatenated JSON documents instead of one array on any PR with enough files, which crashes the parser. I reproduced this myself against a real 293 file PR on a major open source repo before writing the fix, then reproduced the fixed version parsing all 293 files cleanly.
Round three: two smaller but real gaps (a bare secrets/ directory not matching, renamed files only checked under their new name).
Three full rounds of "assume it's broken until proven otherwise" on a single feature, before it ever touched a real repository.
The twist: fixing the very last thing the docs told me to do broke everything
The guard's own documentation had said, since the day I wrote it, that it only actually works if you add it as a required status check in branch protection. Nobody had actually done that setup step yet. So today, I did.
Minutes later, a completely unrelated docs PR got stuck. Every real check on it had passed. GitHub still refused to let it merge.
The cause: the guard job's condition lived at the job level (if: auto_merge != null). On any PR without auto-merge enabled, that makes the entire check run report as skipped, not passed. GitHub's required-status-checks feature does not treat "skipped" as "satisfied." So the one setup step the tool had been telling people to do for weeks would, the moment anyone actually did it, lock every single PR on the repo, not just the auto-merge race it was meant to prevent.
The fix (move the condition down to individual steps instead of the job) got rejected twice more by review before it was allowed to land: once for shipping with no regression test for this exact bug class, and once because the regression test's own "are these two conditions logical opposites" helper turned out to accept a==b && c!=d as the opposite of a!=b && c==d, which is not true (both can be false at once, so neither branch runs). Verified with actual executable code, not just an argument on paper.
And then, once all of that was actually fixed and verified live against the original stuck PR (skipped, before; passed, after), I discovered something that had nothing to do with my code at all: three unrelated GitHub Apps installed on the account (a hosting provider, a deploy tool, an AI coding assistant) each register a check on every single commit and never complete it. Zero check runs, stuck "queued" forever. Turn on a required check anywhere near that, and every PR looks permanently blocked even when the thing you actually asked GitHub to require is green. Not a bug in Muraqib. A reminder that even once your own code is correct, the platform underneath it can still surprise you.
What this actually is
Not a new category. The first time, as far as I could find, that this specific chain (nightly Playwright, automatic issue, AI diagnosis, PR, one weekly email) is packaged as something you can clone and run in an afternoon, for free, instead of paying $89+/month for a hosted version of the same idea.
MIT licensed. The full incident history above, plus a threat model, is public in the repo, not because it looks impressive but because a tool that runs unattended against production with write access to your repo deserves an honest paper trail, not a marketing page that says "we take security seriously."
Repo: https://github.com/holistis/muraqib
If you run a solo project with no QA budget, it might save you a bad week. If you want a black box you never have to think about, it isn't that, you should still read your own PRs.
Top comments (0)