Before the July 2026 GitLab Hackathon I spent a few days reading gitlab-org/cli, the Go source behind glab. I wasn't hunting for crashes. I was looking for places where the project contradicts itself: one part of the code deprecates something, another part still tells you to use it.
Six of those turned into merge requests. Here's the technique, what it found, and two CI traps that almost stopped any of it from landing.
The technique: grep the deprecation, then read the help text
Cobra marks a retired flag with MarkDeprecated. That's the project saying, in code, don't use this. The question is whether the help text on that same command agrees.
So grep every MarkDeprecated call, then read the Use, Long and Example strings on the command that declares it. Two hits.
glab issue list and glab incident list both ship an example using --opened. That flag is hidden, deprecated, and already the default. Copy the example and you get a deprecation warning in exchange for behavior you'd get anyway.
glab mr note --help documents --resolve and --unresolve. Both were deprecated in favor of subcommands, so the help text pushes you onto the path that's being removed.
Neither is dramatic. Both are docs that actively teach the wrong thing, which is worse than missing docs. Nobody double-checks a help example.
Same idea, other contradictions
Once you're grepping for "this part of the project disagrees with that part", the search keeps working.
The bug template tells reporters to use a variable the CLI warns about. It says to run with DEBUG=true. But IsEnvVarEnabled prints a deprecation warning for any variable without the GLAB_ prefix, so following the template puts a deprecation warning inside the bug report. It should be GLAB_DEBUG.
deploy-key delete takes no argument and then deletes ID 0. It's declared Args: cobra.MaximumNArgs(1), but Use, Long and Example all show <key-id> as required. Run it bare and the missing ID falls through as the zero value:
DELETE /projects/:id/deploy_keys/0
and it prints success. Declaration and docs disagree about whether the arg is optional, and the declaration wins.
ci lint lints the error page. It accepts a URL and never checks the status code. Point it at a 404 and the error page body goes straight to the lint endpoint, so you get a syntax error about HTML you never wrote instead of "that URL returned 404".
Array params stop being arrays if they contain a capital letter. glab api -f/-F decides whether a bracketed value is an array with this regex:
^\[\s*([[:lower:]_]+(\s*,\s*[[:lower:]_]+)*)?\s*\]$
Every element has to be lowercase letters and underscores. One hyphen, one capital, one digit, and the whole value silently becomes a JSON string:
glab api -X PUT projects/:id -F "topics=[my-topic, GitLab]"
# sent: {"topics":"[my-topic, GitLab]"}
# expected: {"topics":["my-topic","GitLab"]}
Topics are where this bites, since they're user-authored and full of hyphens and capitals. The fix widens an element to either a quoted string or a run of characters that can't be confused with the list syntax, splits only on commas outside quotes, and returns an empty slice for [] instead of a slice holding one empty string.
I didn't make it parse every [ or { value as JSON. That changes types for existing users, since [1, 2] stops being strings, and it goes well past the bug.
Tests that write outside their temp directory, Windows only. Two unrelated causes, same symptom.
The exec helper in internal/testing/cmdtest/factory.go runs the command line through shlex.Split, which treats backslash as an escape character. A path from t.TempDir() like C:\Users\...\Temp\TestFoo\001 arrives as C:Users...TempTestFoo001. That's a relative path, so the test writes into the working tree.
Separately, two tests relocate the home directory with HOME alone. On Windows os.UserHomeDir reads USERPROFILE, so they resolve to the real home directory and drop an actual ~/.agents/skills/glab/SKILL.md on your machine. config_file_test.go already documents setting both. It just wasn't applied here.
Trap one: a new account can't run shared runners, and the error doesn't say so
Push a branch to your fork and the pipeline dies immediately with zero jobs. No failing job to read, no log, nothing that names a cause.
The reason only shows up if you ask the API for the pipeline's failureReason:
The pipeline failed due to the user not being verified
A new gitlab.com account can't use shared runners until it passes identity verification at gitlab.com/-/identity_verification, by phone or card. No charge. But nothing in the pipeline UI says so, and "zero jobs, no error" looks like a broken .gitlab-ci.yml rather than an account state.
If you made the account for this contribution, do the verification first and save yourself an hour debugging CI config that's fine.
Trap two: pushing a branch doesn't tell you if the MR pipeline passes
gitlab-org/cli filters pipelines through workflow.rules, so a plain branch push doesn't produce the pipeline an MR produces. The green you get from pushing isn't the green you need, and you find that out in public.
To see the real thing, open a throwaway MR inside your own fork, fork branch into fork main. That fires an actual merge_request_event pipeline with the project's rules. Mine came back green across lint, lint_commit, danger-review, check_go_generated_code, tests:unit, tests:integration and the docs jobs. Close it, then submit upstream.
Two smaller things
/copy_metadata doesn't link an MR to an issue. It copies labels and milestone. For the link you need Closes #NNNN in the description, and during a hackathon that link is worth 30 points.
You can add it after the MR exists. Editing the description registers the link the same as writing it at creation time. I assumed otherwise and nearly left the points on the table.
What went in
Six merge requests against gitlab-org/cli, all green:
- !3536 serialize arrays with non-lowercase elements (#8216)
-
!3537 use
GLAB_DEBUGin the bug template and agent guide (#8401) - !3538 stop pointing command help at deprecated flags (#8408)
- !3539 keep command tests inside their temp directories on Windows (#8409)
-
!3540 require a key ID for
deploy-key delete(#8410) - !3541 reject failed responses when linting a remote URL (#8411)
None of this needed deep knowledge of the codebase. It needed a checkout, the project's own CI commands run locally, and treating "the docs say X, the code says Y" as a bug instead of a detail. On a project this size that gap is usually still open.










