[{"data":1,"prerenderedAt":413},["ShallowReactive",2],{"blog-\u002Fblogs\u002Fzero-approvals":3},{"id":4,"title":5,"body":6,"description":403,"extension":404,"meta":405,"navigation":408,"path":409,"seo":410,"stem":411,"__hash__":412},"content\u002Fblogs\u002Fzero-approvals.md","Zero approvals, a wall of checks",{"type":7,"value":8,"toc":387},"minimark",[9,21,26,45,138,141,144,147,150,154,160,181,184,187,190,197,209,303,311,316,323,327,330,333,337,361,365,368,371,374,377,380],[10,11,12,13,20],"p",{},"Hello friends! This is the third post in our series on how ",[14,15,19],"a",{"href":16,"rel":17},"https:\u002F\u002Fquark.autobutler.org",[18],"nofollow","Quark"," gets built by AI\nagents. This post talks about our merge gates and how we keep our release branch healthy.",[22,23,25],"h2",{"id":24},"the-ruleset","The ruleset",[10,27,28,29,38,39,44],{},"Quark's ",[14,30,33,37],{"href":31,"rel":32},"https:\u002F\u002Fgithub.com\u002Fautobutler-org\u002Fquark\u002Ftree\u002Fmain",[18],[34,35,36],"code",{},"main"," branch"," is protected by a single ",[14,40,43],{"href":41,"rel":42},"https:\u002F\u002Fdocs.github.com\u002Fen\u002Frepositories\u002Fconfiguring-branches-and-merges-in-your-repository\u002Fmanaging-rulesets\u002Fabout-rulesets",[18],"repository ruleset",".\nIt blocks branch deletion and force pushes, requires linear history, and only allows squash merges. Importantly, it also\nrequires ten status checks, spread across the frontend and backend, to pass before merging code:",[46,47,48,64],"table",{},[49,50,51],"thead",{},[52,53,54,58,61],"tr",{},[55,56,57],"th",{},"Frontend",[55,59,60],{},"Backend",[55,62,63],{},"Other",[65,66,67,85,99,113,127],"tbody",{},[52,68,69,75,80],{},[70,71,72],"td",{},[34,73,74],{},"check-frontend",[70,76,77],{},[34,78,79],{},"check-backend",[70,81,82],{},[34,83,84],{},"check-misc",[52,86,87,92,97],{},[70,88,89],{},[34,90,91],{},"build-android",[70,93,94],{},[34,95,96],{},"build-backend",[70,98],{},[52,100,101,106,111],{},[70,102,103],{},[34,104,105],{},"build-ios",[70,107,108],{},[34,109,110],{},"test-backend",[70,112],{},[52,114,115,120,125],{},[70,116,117],{},[34,118,119],{},"build-web",[70,121,122],{},[34,123,124],{},"check-migrations",[70,126],{},[52,128,129,134,136],{},[70,130,131],{},[34,132,133],{},"test-frontend",[70,135],{},[70,137],{},[10,139,140],{},"If all status checks pass, the PR is mergeable with no approver.",[10,142,143],{},"Not an accident.",[10,145,146],{},"Not an oversight.",[10,148,149],{},"There is little reason for me to read the code at a gated stage anymore.",[22,151,153],{"id":152},"concerns-from-the-masses","Concerns from the masses",[10,155,156,157,159],{},"I often hear the concerns of people around fully automated code merging as centered around performance and security.\nWhile legitimate to some degree, these are still often better mitigated with automated tooling. For example, we have\nperformance testing on every merge to ",[34,158,36],{},", and as of writing this blog post I will be transparent with you and tell\nyou that we are \"failing\" those currently.",[10,161,162,163,168,169,174,175,180],{},"First off, the definition of \"failure\" here is relative\u002Farbitrary. Our thresholds are not strict, but instead mark off a\npoint where falling under it warrants investigation into either loosening our performance constraints in the test, or identifying\na real issue. In our current streak of failed tests, it seems ",[14,164,167],{"href":165,"rel":166},"https:\u002F\u002Fgithub.com\u002Fautobutler-org\u002Fquark\u002Factions\u002Fruns\u002F39692605456",[18],"the failures","\nmay relate to the merge of some code that ",[14,170,173],{"href":171,"rel":172},"https:\u002F\u002Fgithub.com\u002Fautobutler-org\u002Fquark\u002Fpull\u002F3659",[18],"changed how password vault and chat E2EE key derivation works",".\nIt seems the failure is not around performance, but around unexpected API call failures. So the test demonstrates some regression\nthat should be investigated,\n",[14,176,179],{"href":177,"rel":178},"https:\u002F\u002Fgithub.com\u002Fautobutler-org\u002Fquark\u002Fissues\u002F3771",[18],"which is being tracked here",".",[10,182,183],{},"Now I need you to be honest with yourself for a moment here: how often have you gated code on performance regressions? I\nimagine that some of you, working in bespoke problem spaces, will answer yes and I applaud you for doing your due diligence.\nI imagine the grand majority of devs are not even bothering with this sort of thing. I think those who don't care at all\nare often acting foolishly, and that is why we have opted for non-gating performance testing as a means of providing a canary\nin the coal mine for new and unresolved behavioral issues with the application.",[10,185,186],{},"With that canary present, we feel free to let the agents loose on the code, occasionally checking the canary for a heartbeat.\nEven when the canary dies, as it has up above, this just triggers a \"cleanup\" session on the frontend or backend, or wherever\nthe issue is present. Every few months I have spent a few days cleaning up the crust that has built up in the codebase,\noften starting with a manual review of the code, and then guiding agents towards the intended state. A few thousand lines\nof code removed later, the code is healthy, vibrant, and ready to be changed again. How often do you enjoy this luxury on\nconventional human-developed software projects? That question is left as an exercise for the reader.",[10,188,189],{},"Now, back to what we are doing.",[22,191,193,194],{"id":192},"what-is-check","What is ",[34,195,196],{},"check",[10,198,199,200,208],{},"Most of our merge gating comes down to ",[14,201,204,205],{"href":202,"rel":203},"https:\u002F\u002Fgithub.com\u002Fautobutler-org\u002Fquark\u002Fblob\u002Fda8e305e1afa82f43edbf9ecc275221577909a7f\u002FMakefile#L1571",[18],"a single Makefile target: ",[34,206,207],{},"make check",". It runs a suite like the following:",[46,210,211,221],{},[49,212,213],{},[52,214,215,217,219],{},[55,216,57],{},[55,218,60],{},[55,220,63],{},[65,222,223,239,256,269,281,294],{},[52,224,225,230,236],{},[70,226,227],{},[34,228,229],{},"dart format --set-exit-if-changed",[70,231,232,233],{},"gofmt and ",[34,234,235],{},"go vet",[70,237,238],{},"spellcheck",[52,240,241,247,254],{},[70,242,243,246],{},[34,244,245],{},"flutter analyze"," (info is fatal)",[70,248,249],{},[14,250,253],{"href":251,"rel":252},"https:\u002F\u002Fgolangci-lint.run\u002F",[18],"golangci-lint",[70,255],{},[52,257,258,260,267],{},[70,259],{},[70,261,262],{},[14,263,266],{"href":264,"rel":265},"https:\u002F\u002Fstaticcheck.dev\u002F",[18],"staticcheck",[70,268],{},[52,270,271,273,279],{},[70,272],{},[70,274,275,278],{},[34,276,277],{},"check-go-structure.bash"," for backend layout",[70,280],{},[52,282,283,285,292],{},[70,284],{},[70,286,287],{},[14,288,291],{"href":289,"rel":290},"https:\u002F\u002Fsqlc.dev\u002F",[18],"sqlc",[70,293],{},[52,295,296,298,301],{},[70,297],{},[70,299,300],{},"migration number collision",[70,302],{},[10,304,305,306,310],{},"You should be doing this in all codebases, human or AI-coded. You will find immeasurable long-term benefit off of forcing\neveryone to code in a consistent manner, and these are cheap ways to gate exactly that. Get creative too. My favorite one\nhere is ",[14,307,277],{"href":308,"rel":309},"https:\u002F\u002Fgithub.com\u002Fautobutler-org\u002Fquark\u002Fblob\u002Fda8e305e1afa82f43edbf9ecc275221577909a7f\u002Fscripts\u002Fcheck-go-structure.bash",[18],",\na custom check to assert that the Go code modules put their code in the right \"semantic\" file locations. Say I want all\nmodules to declare types in one file, and have implementations in another file, we assert that here and fail PRs that stray\noff the path. This guarantees that the AI agents can't hallucinate new project layouts, and prevents humans from doing the\nsame. That way everyone going into a module of our project knows where to find the code they are searching for, with or\nwithout an IDE.",[312,313,315],"h3",{"id":314},"generated-code","Generated code",[10,317,318,319,322],{},"We have a lot of generated code, such as that which ",[14,320,291],{"href":289,"rel":321},[18]," creates, and we make sure on all PRs that\nthe committed code is the same as what is generated. By putting this in CI, it prevents and flags drifting generation tool\nversions on dev and AI workspaces, while also guaranteeing no edits were made post-generation to generated files.",[312,324,326],{"id":325},"checks-are-valuable-to-all","Checks are valuable to all",[10,328,329],{},"The biggest lesson to take from this is that all code, human or AI-written, should be tightly gated, conformed, and checked.",[10,331,332],{},"Never trust anyone writing code without tools like these.",[22,334,336],{"id":335},"copy-this","Copy this",[338,339,340,344,351,354],"ul",{},[341,342,343],"li",{},"A pre-commit hook that runs every formatter and linter and no tests. Put the full suite in CI and have the agent run\nscoped tests before it commits.",[341,345,346,347,350],{},"Regenerate everything in CI, then ",[34,348,349],{},"git diff --exit-code",". It's the cheapest way to stop an agent from hand-editing\ngenerated files.",[341,352,353],{},"A spellchecker over the whole repo. It's a simple check for invented names and a free American-spelling enforcer (🇺🇸).",[341,355,356,357,360],{},"Branch protection with ",[34,358,359],{},"required_approving_review_count: 0",". If you aren't going to read the diffs, don't pretend\nyou are.",[22,362,364],{"id":363},"whats-next","What's next",[10,366,367],{},"Most of these checks showed up after something went wrong first. Next up is \"Every rule is a scar\": how a rule gets\nwritten down after one violation, and how it becomes a script after a second, starting with our\npre-commit hook.",[10,369,370],{},"As always, we love you all and we want to build stuff for you. Feel free to reach out anytime.",[10,372,373],{},"Grace and peace,\nJames",[375,376],"hr",{},[10,378,379],{},"This is part 3 of 9 in our series on how we build Quark with AI agents.",[10,381,382,383],{},"← Previous: ",[14,384,386],{"href":385},"\u002Fblogs\u002Fthe-issue-is-the-prompt","The issue is the prompt",{"title":388,"searchDepth":389,"depth":390,"links":391},"",6,5,[392,394,395,401,402],{"id":24,"depth":393,"text":25},2,{"id":152,"depth":393,"text":153},{"id":192,"depth":393,"text":396,"children":397},"What is check",[398,400],{"id":314,"depth":399,"text":315},3,{"id":325,"depth":399,"text":326},{"id":335,"depth":393,"text":336},{"id":363,"depth":393,"text":364},"Our main branch needs no human approval, but requires many passing checks. Here is why that split works.","md",{"date":406,"author":407},"2026-10-26","James Orson",true,"\u002Fblogs\u002Fzero-approvals",{"title":5,"description":403},"blogs\u002Fzero-approvals","NN9o0LbS-7PTFnu0iNGp0asyohamVQ0_xlhYGrEEnBc",1793083529859]