<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Golangci-Lint on PHP Boy Scout</title><link>https://blog-570662.gitlab.io/tags/golangci-lint/</link><description>Recent content in Golangci-Lint on PHP Boy Scout</description><generator>Hugo -- gohugo.io</generator><language>en-gb</language><copyright>Matt Cockayne</copyright><lastBuildDate>Fri, 26 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog-570662.gitlab.io/tags/golangci-lint/index.xml" rel="self" type="application/rss+xml"/><item><title>The agent said SUCCESS. The linter disagreed.</title><link>https://blog-570662.gitlab.io/the-agent-said-success-the-linter-disagreed/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate><guid>https://blog-570662.gitlab.io/the-agent-said-success-the-linter-disagreed/</guid><description>&lt;img src="https://blog-570662.gitlab.io/the-agent-said-success-the-linter-disagreed/cover-the-agent-said-success-the-linter-disagreed.png" alt="Featured image of post The agent said SUCCESS. The linter disagreed." /&gt;&lt;p&gt;There&amp;rsquo;s a repair agent inside go-tool-base now. When you run &lt;a class="link" href="https://blog-570662.gitlab.io/generate-a-command-from-a-script-or-a-sentence/" &gt;&lt;code&gt;gtb generate command&lt;/code&gt;&lt;/a&gt;, it doesn&amp;rsquo;t just spit out a file and wish you luck. An agent takes the generated code, builds it, runs the tests, and fixes whatever it broke, looping until the thing actually works (or until it&amp;rsquo;s tried the same fix five times and admits defeat). The whole point is that the generator hands you code that&amp;rsquo;s ready, not code that&amp;rsquo;s nearly ready and quietly now your problem.&lt;/p&gt;
&lt;p&gt;So it stung a bit when I realised the agent had been holding itself to a lower bar than I&amp;rsquo;d hold any junior to. And I was the one who&amp;rsquo;d set the bar.&lt;/p&gt;
&lt;h2 id="what-done-meant-to-the-agent"&gt;What &amp;ldquo;done&amp;rdquo; meant to the agent
&lt;/h2&gt;&lt;p&gt;The agent is a loop with real tools: it can build, test, read files, write files, tidy the module, and run golangci-lint. It works through them, and when it&amp;rsquo;s happy it replies with the word &amp;ldquo;SUCCESS&amp;rdquo; and the loop stops. On the Go side, the check is exactly that blunt:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToUpper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;SUCCESS&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s the whole gate (&lt;a class="link" href="https://gitlab.com/phpboyscout/go-tool-base/-/blob/4834246/internal/generator/verifier/agent.go#L149-L154" target="_blank" rel="noopener"
 &gt;&lt;code&gt;agent.go&lt;/code&gt;&lt;/a&gt;). There&amp;rsquo;s no clever verification on my end that the agent actually did its homework. It does the work, it tells me it&amp;rsquo;s done, and I believe it. Which is fine, as long as the agent and I agree on what &amp;ldquo;done&amp;rdquo; means.&lt;/p&gt;
&lt;p&gt;We didn&amp;rsquo;t.&lt;/p&gt;
&lt;h2 id="the-instruction-that-made-lint-optional"&gt;The instruction that made lint optional
&lt;/h2&gt;&lt;p&gt;The agent decides it&amp;rsquo;s finished by following a numbered list in its system prompt. Here&amp;rsquo;s the line that did the damage:&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;ol start="4"&gt;
&lt;li&gt;If there are lint issues, use &amp;lsquo;golangci_lint&amp;rsquo;.&lt;/li&gt;
&lt;/ol&gt;

 &lt;/blockquote&gt;
&lt;p&gt;Read that the way the agent would. &amp;ldquo;If there are lint issues&amp;rdquo;&amp;hellip; well, how would it know? The only way to find out is to run golangci-lint. But the instruction makes running golangci-lint the thing you do &lt;em&gt;once you already know&lt;/em&gt; there are issues. It&amp;rsquo;s a chicken with no egg. And the SUCCESS condition at the bottom of the list never mentioned lint at all:&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;ol start="7"&gt;
&lt;li&gt;When the project builds successfully and tests pass, reply with &amp;ldquo;SUCCESS&amp;rdquo;.&lt;/li&gt;
&lt;/ol&gt;

 &lt;/blockquote&gt;
&lt;p&gt;So the agent did the sensible thing, given its orders. It built the code, ran the tests, saw both go green, and declared victory. golangci-lint was sat right there in its toolbox, unused, because nothing ever told it the job wasn&amp;rsquo;t finished until lint was clean too. I&amp;rsquo;d handed it a linter and then written a prompt that let it walk straight past it.&lt;/p&gt;
&lt;p&gt;The galling part is that the linter was never the missing piece. The &lt;a class="link" href="https://gitlab.com/phpboyscout/go-tool-base/-/blob/4834246/internal/agent/tools.go#L539-L550" target="_blank" rel="noopener"
 &gt;&lt;code&gt;golangci_lint&lt;/code&gt; tool&lt;/a&gt; had been registered the whole time, and it even runs with &lt;code&gt;--fix&lt;/code&gt;, so it&amp;rsquo;ll quietly clear the trivial stuff and only surface what actually needs a decision. The capability was there. The instructions just never required it.&lt;/p&gt;
&lt;h2 id="the-fix-was-words-not-code"&gt;The fix was words, not code
&lt;/h2&gt;&lt;p&gt;Here&amp;rsquo;s the part I find genuinely interesting. I didn&amp;rsquo;t add a check. There is no new gate in the Go. The &lt;a class="link" href="https://gitlab.com/phpboyscout/go-tool-base/-/blob/4834246/internal/generator/verifier/agent.go#L128-L134" target="_blank" rel="noopener"
 &gt;fix&lt;/a&gt; is four lines of &lt;em&gt;English&lt;/em&gt;:&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;ol start="2"&gt;
&lt;li&gt;
&lt;p&gt;Run &amp;lsquo;go_build&amp;rsquo;, &amp;lsquo;go_test&amp;rsquo; and &amp;lsquo;golangci_lint&amp;rsquo; in the project directory&amp;hellip; Run all three; a clean build and passing tests do not imply clean lint.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reply with &amp;ldquo;SUCCESS&amp;rdquo; only once &amp;lsquo;go_build&amp;rsquo;, &amp;lsquo;go_test&amp;rsquo; AND &amp;lsquo;golangci_lint&amp;rsquo; all pass with no errors and no reported issues.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

 &lt;/blockquote&gt;
&lt;p&gt;That&amp;rsquo;s it. Lint moves from a remediation step you reach for once you somehow already know there&amp;rsquo;s a problem, into the gate itself. &amp;ldquo;Done&amp;rdquo; now means three green lights, not two.&lt;/p&gt;
&lt;p&gt;It nags at me a little, that one. The reliability of an agent that writes and fixes real code came down to whether one sentence of instructions was precise enough. When your success criteria are a paragraph of prose, vagueness in that paragraph is a bug, the same as a vague type or an off-by-one. The spec just happens to be written in English, and the thing reading it is a language model that will cheerfully take the cheap reading if you leave it lying around. That&amp;rsquo;s the same lesson the &lt;a class="link" href="https://blog-570662.gitlab.io/the-goblin-that-wouldnt-stay-dead/" &gt;goblin who wouldn&amp;rsquo;t stay dead&lt;/a&gt; taught me from the other direction: with these tools, what you say is what you get, and what you &lt;em&gt;don&amp;rsquo;t&lt;/em&gt; say is fair game.&lt;/p&gt;
&lt;h2 id="leave-it-better-not-just-building"&gt;Leave it better, not just building
&lt;/h2&gt;&lt;p&gt;The Boy Scout Rule is the whole reason this blog exists, and I&amp;rsquo;d quietly exempted the robot from it. &lt;a class="link" href="https://blog-570662.gitlab.io/the-campsite-was-never-the-point/" &gt;&amp;ldquo;Leave the campsite cleaner than you found it&amp;rdquo;&lt;/a&gt; had become &amp;ldquo;leave it building&amp;rdquo;, which is not the same thing and never was. If I&amp;rsquo;m going to put an agent in the loop precisely so it tidies up after the generator, then &amp;ldquo;tidy&amp;rdquo; has to mean what it would mean for a person on my team. Build, test &lt;em&gt;and&lt;/em&gt; lint. No walking past the bin because nobody told you to pick it up.&lt;/p&gt;</description></item></channel></rss>