<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Lee Seonghyeon · 이성현</title>
    <description>The latest articles on DEV Community by Lee Seonghyeon · 이성현 (@ihopenreeng).</description>
    <link>https://dev.to/ihopenreeng</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4042966%2F27739f9c-5ea6-4b7e-a5d2-83f0f02640e4.png</url>
      <title>DEV Community: Lee Seonghyeon · 이성현</title>
      <link>https://dev.to/ihopenreeng</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ihopenreeng"/>
    <language>en</language>
    <item>
      <title>Six ways GitLab CLI documented itself wrong, and the two CI traps I hit finding them</title>
      <dc:creator>Lee Seonghyeon · 이성현</dc:creator>
      <pubDate>Thu, 23 Jul 2026 04:25:12 +0000</pubDate>
      <link>https://dev.to/ihopenreeng/six-ways-gitlab-cli-documented-itself-wrong-and-the-two-ci-traps-i-hit-finding-them-3ddg</link>
      <guid>https://dev.to/ihopenreeng/six-ways-gitlab-cli-documented-itself-wrong-and-the-two-ci-traps-i-hit-finding-them-3ddg</guid>
      <description>&lt;p&gt;Before the July 2026 GitLab Hackathon I spent a few days reading &lt;code&gt;gitlab-org/cli&lt;/code&gt;, the Go source behind &lt;code&gt;glab&lt;/code&gt;. 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The technique: grep the deprecation, then read the help text
&lt;/h2&gt;

&lt;p&gt;Cobra marks a retired flag with &lt;code&gt;MarkDeprecated&lt;/code&gt;. That's the project saying, in code, don't use this. The question is whether the help text on that same command agrees.&lt;/p&gt;

&lt;p&gt;So grep every &lt;code&gt;MarkDeprecated&lt;/code&gt; call, then read the &lt;code&gt;Use&lt;/code&gt;, &lt;code&gt;Long&lt;/code&gt; and &lt;code&gt;Example&lt;/code&gt; strings on the command that declares it. Two hits.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;glab issue list&lt;/code&gt; and &lt;code&gt;glab incident list&lt;/code&gt; both ship an example using &lt;code&gt;--opened&lt;/code&gt;. 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.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;glab mr note --help&lt;/code&gt; documents &lt;code&gt;--resolve&lt;/code&gt; and &lt;code&gt;--unresolve&lt;/code&gt;. Both were deprecated in favor of subcommands, so the help text pushes you onto the path that's being removed.&lt;/p&gt;

&lt;p&gt;Neither is dramatic. Both are docs that actively teach the wrong thing, which is worse than missing docs. Nobody double-checks a help example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same idea, other contradictions
&lt;/h2&gt;

&lt;p&gt;Once you're grepping for "this part of the project disagrees with that part", the search keeps working.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The bug template tells reporters to use a variable the CLI warns about.&lt;/strong&gt; It says to run with &lt;code&gt;DEBUG=true&lt;/code&gt;. But &lt;code&gt;IsEnvVarEnabled&lt;/code&gt; prints a deprecation warning for any variable without the &lt;code&gt;GLAB_&lt;/code&gt; prefix, so following the template puts a deprecation warning inside the bug report. It should be &lt;code&gt;GLAB_DEBUG&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;deploy-key delete&lt;/code&gt; takes no argument and then deletes ID 0.&lt;/strong&gt; It's declared &lt;code&gt;Args: cobra.MaximumNArgs(1)&lt;/code&gt;, but &lt;code&gt;Use&lt;/code&gt;, &lt;code&gt;Long&lt;/code&gt; and &lt;code&gt;Example&lt;/code&gt; all show &lt;code&gt;&amp;lt;key-id&amp;gt;&lt;/code&gt; as required. Run it bare and the missing ID falls through as the zero value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;DELETE /projects/:id/deploy_keys/0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and it prints success. Declaration and docs disagree about whether the arg is optional, and the declaration wins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;ci lint&lt;/code&gt; lints the error page.&lt;/strong&gt; 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".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Array params stop being arrays if they contain a capital letter.&lt;/strong&gt; &lt;code&gt;glab api -f/-F&lt;/code&gt; decides whether a bracketed value is an array with this regex:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;^\[\s*([[:lower:]_]+(\s*,\s*[[:lower:]_]+)*)?\s*\]$
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every element has to be lowercase letters and underscores. One hyphen, one capital, one digit, and the whole value silently becomes a JSON string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;glab api &lt;span class="nt"&gt;-X&lt;/span&gt; PUT projects/:id &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"topics=[my-topic, GitLab]"&lt;/span&gt;
&lt;span class="c"&gt;# sent:     {"topics":"[my-topic, GitLab]"}&lt;/span&gt;
&lt;span class="c"&gt;# expected: {"topics":["my-topic","GitLab"]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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 &lt;code&gt;[]&lt;/code&gt; instead of a slice holding one empty string.&lt;/p&gt;

&lt;p&gt;I didn't make it parse every &lt;code&gt;[&lt;/code&gt; or &lt;code&gt;{&lt;/code&gt; value as JSON. That changes types for existing users, since &lt;code&gt;[1, 2]&lt;/code&gt; stops being strings, and it goes well past the bug.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tests that write outside their temp directory, Windows only.&lt;/strong&gt; Two unrelated causes, same symptom.&lt;/p&gt;

&lt;p&gt;The exec helper in &lt;code&gt;internal/testing/cmdtest/factory.go&lt;/code&gt; runs the command line through &lt;code&gt;shlex.Split&lt;/code&gt;, which treats backslash as an escape character. A path from &lt;code&gt;t.TempDir()&lt;/code&gt; like &lt;code&gt;C:\Users\...\Temp\TestFoo\001&lt;/code&gt; arrives as &lt;code&gt;C:Users...TempTestFoo001&lt;/code&gt;. That's a relative path, so the test writes into the working tree.&lt;/p&gt;

&lt;p&gt;Separately, two tests relocate the home directory with &lt;code&gt;HOME&lt;/code&gt; alone. On Windows &lt;code&gt;os.UserHomeDir&lt;/code&gt; reads &lt;code&gt;USERPROFILE&lt;/code&gt;, so they resolve to the real home directory and drop an actual &lt;code&gt;~/.agents/skills/glab/SKILL.md&lt;/code&gt; on your machine. &lt;code&gt;config_file_test.go&lt;/code&gt; already documents setting both. It just wasn't applied here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap one: a new account can't run shared runners, and the error doesn't say so
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The reason only shows up if you ask the API for the pipeline's &lt;code&gt;failureReason&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The pipeline failed due to the user not being verified
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A new gitlab.com account can't use shared runners until it passes identity verification at &lt;code&gt;gitlab.com/-/identity_verification&lt;/code&gt;, by phone or card. No charge. But nothing in the pipeline UI says so, and "zero jobs, no error" looks like a broken &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; rather than an account state.&lt;/p&gt;

&lt;p&gt;If you made the account for this contribution, do the verification first and save yourself an hour debugging CI config that's fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap two: pushing a branch doesn't tell you if the MR pipeline passes
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;gitlab-org/cli&lt;/code&gt; filters pipelines through &lt;code&gt;workflow.rules&lt;/code&gt;, 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.&lt;/p&gt;

&lt;p&gt;To see the real thing, open a throwaway MR inside your own fork, fork branch into fork &lt;code&gt;main&lt;/code&gt;. That fires an actual &lt;code&gt;merge_request_event&lt;/code&gt; pipeline with the project's rules. Mine came back green across lint, &lt;code&gt;lint_commit&lt;/code&gt;, &lt;code&gt;danger-review&lt;/code&gt;, &lt;code&gt;check_go_generated_code&lt;/code&gt;, &lt;code&gt;tests:unit&lt;/code&gt;, &lt;code&gt;tests:integration&lt;/code&gt; and the docs jobs. Close it, then submit upstream.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two smaller things
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/copy_metadata&lt;/code&gt; doesn't link an MR to an issue. It copies labels and milestone. For the link you need &lt;code&gt;Closes #NNNN&lt;/code&gt; in the description, and during a hackathon that link is worth 30 points.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  What went in
&lt;/h2&gt;

&lt;p&gt;Six merge requests against &lt;code&gt;gitlab-org/cli&lt;/code&gt;, all green:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://gitlab.com/gitlab-org/cli/-/merge_requests/3536" rel="noopener noreferrer"&gt;!3536&lt;/a&gt; serialize arrays with non-lowercase elements (&lt;a href="https://gitlab.com/gitlab-org/cli/-/issues/8216" rel="noopener noreferrer"&gt;#8216&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gitlab.com/gitlab-org/cli/-/merge_requests/3537" rel="noopener noreferrer"&gt;!3537&lt;/a&gt; use &lt;code&gt;GLAB_DEBUG&lt;/code&gt; in the bug template and agent guide (&lt;a href="https://gitlab.com/gitlab-org/cli/-/issues/8401" rel="noopener noreferrer"&gt;#8401&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gitlab.com/gitlab-org/cli/-/merge_requests/3538" rel="noopener noreferrer"&gt;!3538&lt;/a&gt; stop pointing command help at deprecated flags (&lt;a href="https://gitlab.com/gitlab-org/cli/-/issues/8408" rel="noopener noreferrer"&gt;#8408&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gitlab.com/gitlab-org/cli/-/merge_requests/3539" rel="noopener noreferrer"&gt;!3539&lt;/a&gt; keep command tests inside their temp directories on Windows (&lt;a href="https://gitlab.com/gitlab-org/cli/-/issues/8409" rel="noopener noreferrer"&gt;#8409&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gitlab.com/gitlab-org/cli/-/merge_requests/3540" rel="noopener noreferrer"&gt;!3540&lt;/a&gt; require a key ID for &lt;code&gt;deploy-key delete&lt;/code&gt; (&lt;a href="https://gitlab.com/gitlab-org/cli/-/issues/8410" rel="noopener noreferrer"&gt;#8410&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gitlab.com/gitlab-org/cli/-/merge_requests/3541" rel="noopener noreferrer"&gt;!3541&lt;/a&gt; reject failed responses when linting a remote URL (&lt;a href="https://gitlab.com/gitlab-org/cli/-/issues/8411" rel="noopener noreferrer"&gt;#8411&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

</description>
      <category>gitlab</category>
      <category>opensource</category>
      <category>go</category>
      <category>cli</category>
    </item>
  </channel>
</rss>
