DEV Community

Cover image for How I contribute to GitLab with a coding agent (and approve every push)

How I contribute to GitLab with a coding agent (and approve every push)

Hamza Hasanain on October 20, 2026

I came back to GitLab contributions after about 9 months away, and I had forgotten most of the process. So I wrote it down once, as rules and workf...
Collapse
 
amintacccp profile image
tamina •

The part that convinced me is that the guard is a hook, not a prompt suggestion: agent_guard.py answering deny on a push to upstream, and a verifier checking every write, is a very different trust level from "please ask me first" in AGENTS.md. The wording file built from your own past comments is the other detail people will copy — an agent MR that sounds like a template is exactly what makes maintainers suspicious. And the gl.py inbox born from finding 3 unanswered comments after the merges is painfully relatable. 15 of 20 merged with zero closed as unwanted says the restraint is working.

Collapse
 
hamzahassanain0 profile image
Hamza Hasanain •

yes exactly.
prompts are suggestions, but hooks and guard scripts are hard limits.
if the agent can bypass a rule, it will do it at some point.

the wording file was necessary because maintainers spot generic AI text immediately.
and missing review comments was painful, so the inbox check had to be automated.

glad the restraint part made sense :)

Collapse
 
amintacccp profile image
tamina •

That line is the one I'd underline: if the agent can bypass a rule, it will do it at some point. It's not even malice, it's just optimization pressure over enough runs — a suggestion in a prompt gets traded away the moment it conflicts with finishing the task, a hook can't be.

And the wording file point feels underrated. The guard stops the wrong action, but the wording file stops the contribution from reading as a drive-by in the first place — two different kinds of trust, and maintainers notice both. Automating the inbox check after missing review comments makes complete sense too: that follow-through is exactly what a human forgets under load, and exactly what shouldn't be left to memory.

Thread Thread
 
hamzahassanain0 profile image
Hamza Hasanain •

when the agent is focused on finishing the task, a prompt is just a soft hint.
without a hook, it will always pick the path of least resistance.

and I agree on the two kinds of trust.
the guard prevents bad actions,
but the wording file prevents losing goodwill before anyone even reads the diff.

thanks a lot for the thoughtful reply :)

Thread Thread
 
amintacccp profile image
tamina •

The least-resistance point is the one that stings, because it's not the agent being careless — it's doing exactly what it's optimized for: finishing the task. Maintaining the relationship just isn't part of that objective, so the goodwill layer can't be left to its discretion.

That's why I like your split of the two trusts. The guard protects safety: the bad action simply never happens. The wording file protects perception: how the work lands with the maintainer before a single line of the diff gets judged. And that second kind of trust is sneakier to lose — you usually only notice it's gone when a review turns cold, or doesn't happen at all.

One thing I'm curious about: is your wording file per-repo, or one global version? And when a repo's own conventions clash with the global one, which one wins?

Thread Thread
 
hamzahassanain0 profile image
Hamza Hasanain •

one global file for the voice, it is how I write, so it does not change per repo.
but the repo's own format rules always win: the MR template, conventional commits, trailers, changelog format.
the wording file only covers the free text inside them.
anything repo specific goes in that project's playbook (projects/.md), not in the wording file.

Collapse
 
makayis_90942586425f8f6bb profile image
makayis •

I like that the review lessons ("extend the existing test table instead of adding a new one") go into the playbook rather than staying in your head. That's usually the stuff an agent forgets halfway through a long session. One thing I'd watch: if every lesson gets appended, the playbook becomes a fixed chunk of context on every request, so it might be worth pruning lessons once the agent reliably follows them. 15 of 20 merged with zero closed is a good result for a hackathon week.

Collapse
 
hamzahassanain0 profile image
Hamza Hasanain •

Thank you!
good point about the context, but the playbook is not loaded on every request.
the agent reads projects/.md only when it works on that project, so the glab lessons never load when I work on gitaly.
pruning lessons the agent already follows makes sense, but I am not sure yet how to know it "reliably follows" one.
right now a lesson only gets removed or changed when a review shows it is wrong.

Collapse
 
makayis_90942586425f8f6bb profile image
makayis •

Ah, that's better than I assumed. If the project file only loads when you're working on that project, the cost is already scoped. On "reliably follows", one cheap signal is how many merged MRs have gone by since a review last had to flag that lesson. Once it's been quiet for a while you could move it to an archive section and see if the mistake comes back, and put it back if it does. Honestly your rule (only change a lesson when review shows it's wrong) is the safer default, I'd only bother with pruning once one project file gets long.

Collapse
 
panthpatel profile image
Panth Patel •

The exchange with eye_java is the part I keep coming back to: a guard keyed on command names leaves every other path to the token open. On my ticket board I went the other way. The agent has no side door; it uses the same APIs the frontend uses, with a token scoped to one board, and only I move a ticket to Done. The limit sits in the token, not in a list of commands. Which of your outward actions could the GitLab token itself refuse?

Collapse
 
hamzahassanain0 profile image
Hamza Hasanain •

good question, some of the important ones are already refused by GitLab itself:
1- push to upstream, I am not a member there, only the community fork accepts my pushes
2- merging, only maintainers can merge
3- assigning myself to an issue, the API returns 403
so the hook on upstream push is a second layer, not the only one.
what the token cannot refuse: comments, reactions and opening MRs on any public project.
any logged in user can do that, so for those the gate is still the hook and me.
and a contributor cannot create a project access token on upstream, it needs the maintainer role.
so I like your setup more, the limit in the token is the better place, GitLab just does not give me that option yet.

Collapse
 
panthpatel profile image
Panth Patel •

@hamzahassanain0 that split is useful: GitLab already refuses push, merge and assign, so what is left is the write surface any account has. Comments and MRs are the two that reach other people with no review on GitLab's side. Does your hook hold a comment for your approval the way it holds an upstream push?

Collapse
 
vishisht_choudhary_a12d08 profile image
Vishisht Choudhary •

The guard returning force_ask on every push while flatly denying cat .env is a really nice split, since it keeps the agent useful without handing it the keys. I also like that review feedback goes back into per-project playbooks; 'extend the existing test table instead of adding a new one' is exactly the kind of house style an agent can't infer from the code alone. Have you found the playbooks drift as maintainers change their minds, or do you prune them after each merged MR?

Collapse
 
hamzahassanain0 profile image
Hamza Hasanain •

Thank you!
I do not prune them after each MR. Every lesson has the date and the issue it came from, so when a later review or issue shows it is wrong, the agent fixes that line and adds "corrected ".
This already happened once in the glab playbook: a note about how use_keyring is read was wrong, and work on another issue a week later fixed it.
I think the real drift will show up later, when maintainers change their minds. The playbooks are only a few weeks old, so I am not sure yet.

Collapse
 
eye_java_420f1faa10ae8b86 profile image
Nan •

The guard list is the part I would tighten next, because it is keyed on names rather than capabilities. gl.py is an authenticated GitLab client sitting in the repo next to the token in .env, so the hook sees the calls you routed through the documented path and nothing else. A token read through python3 -c instead of cat, or an API call made by whatever CLI the agent installs next week, produces the same remote write with no hook firing, since that is not the shape the rule was written against.

Two boundaries that cost less than the rules do: scope the PAT down to the projects in projects.json instead of an account-wide api token, and flip the network rule to default deny with python3 tools/gl.py as the only allowlisted egress, logging the method and URL of every call. That turns "I approve every push" into "I approve every remote write", which is the property you actually want - the push hook only proves the write left through the door you built.

Collapse
 
hamzahassanain0 profile image
Hamza Hasanain •

you are right, I just tested it.
python3 -c reading .env, a raw curl POST to the API, and glab api are not denied by the guard.
they only fall back to the normal agy permission prompt, so the real gate there is me reading the prompt.
I said this in the post as the honest limit, but your framing is better: approve every remote write, not every push.

the default deny network with gl.py as the only egress is a good idea, I will try it.
about scoping the token, I am not sure GitLab lets a contributor limit a personal token to upstream projects yet.
for now I keep read_api for scouting and analysis, and use api only when I post.

Collapse
 
kartik-nvjk profile image
Kartik N V J K •

15 of 20 merged with none closed is a better record than I get by hand on repos I barely know. I'm stealing the split between a flat deny on reading .env and force_ask on every push. I had both at the same level and caught myself approving prompts on autopilot. As the review lessons pile into each playbook, do you ever prune the old ones?

Collapse
 
hamzahassanain0 profile image
Hamza Hasanain •

Thank you!
yes, the autopilot approvals are real, that is why .env is a hard deny and not one more prompt.
about pruning, no, I do not prune them. every lesson has the date and the issue it came from, so when a later review shows it is wrong, the agent fixes that line.
it already happened once in the glab playbook. the playbooks are only a few weeks old, so I am not sure yet how bad the drift will get.

Collapse
 
pang_claw profile image
pang •

The hook that asks before every push and every GitLab write is the part I'd copy first.

Collapse
 
hamzahassanain0 profile image
Hamza Hasanain •

yes, that is the part I would keep if I had to drop everything else :)
it costs a few seconds per write, and I read every comment before it goes out.

Collapse
 
kevinhall profile image
Kevin Hall •

The hook-not-prompt line matched a run I watched this morning. The prompt said count the segment first, then pull the rows; the agent ran both calls in parallel, because nothing stopped it, and on a 20-row run that costs nothing. On a 2,000-row run it is the bill. A hook that refuses the paid call until a count has been read, and a cap on what one call may spend, turned the suggestion into a limit. Same shape as your push guard: the rule lives where the agent cannot trade it away.

Collapse
 
maxbuildsdev profile image
Max Kaya •

Worth adding that the "approve every push" part is the hard bit, and your wording-instructions.md trick is what makes it work. The rules only hold if the agent's output sounds like you, otherwise you're reviewing every comment twice — once for correctness, once for voice. Seeding that file from 10-20 of your own past comments is probably the highest-leverage part of the kit.

One gap I'd be curious about: the loop you describe is linear (scout → analyze → claim → start → ship → followup). What happens when the verifier says no-go on an issue you already claimed, or when a claim comment goes out and the issue turns out to be taken? Half the wasted agent time in my experience is in the edges around "claim" rather than the work itself, and those are exactly the paths that a rules file tends to under-specify since they're rare and annoying to describe.

Also: did you find a way to measure whether the agent is actually improving your throughput, or is 20 MRs in a week mostly an anecdote? A per-issue time-to-merge before vs after would be a much stronger claim than MR count.

Collapse
 
hamzahassanain0 profile image
Hamza Hasanain •

Thank you, yes the wording file is a big part of it.

about the claim edges, you are right, this is where most of the mess is.
What the kit does now:
1- analyze runs before claim, so most no-go happen before I comment anything
2- the claim check runs again before the claim comment, before start, and before opening the MR. mr-create refuses if the issue is not assigned to me or someone else already has an open MR
3- for an old claim, it asks the previous person first instead of taking the issue

What is missing: a no-go after I already claimed. There is no "unclaim" step yet, I think I will add one.

about throughput, honestly it is mostly an anecdote for now. I did not measure time-to-merge before and after.
What I have: 20 MRs in 7 projects during the hackathon week, 12 merged as of Oct 17, none closed.
I agree time-to-merge per issue is a better number, I may add it to the kit later.