<?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: Dekita</title>
    <description>The latest articles on DEV Community by Dekita (@dekita).</description>
    <link>https://dev.to/dekita</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%2F4153801%2F0a63969e-7c65-475c-b266-029ef729f71c.png</url>
      <title>DEV Community: Dekita</title>
      <link>https://dev.to/dekita</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dekita"/>
    <language>en</language>
    <item>
      <title>Your coding agent will call a function that does not exist. Here is the fix.</title>
      <dc:creator>Dekita</dc:creator>
      <pubDate>Fri, 02 Oct 2026 01:28:27 +0000</pubDate>
      <link>https://dev.to/dekita/your-coding-agent-will-call-a-function-that-does-not-exist-here-is-the-fix-2f25</link>
      <guid>https://dev.to/dekita/your-coding-agent-will-call-a-function-that-does-not-exist-here-is-the-fix-2f25</guid>
      <description>&lt;h1&gt;
  
  
  Your coding agent will call a function that does not exist
&lt;/h1&gt;

&lt;p&gt;You ask the agent to add a section to a site. It writes the template on the first try. The template calls a helper function, passes the right arguments, and looks like every other template in the codebase.&lt;/p&gt;

&lt;p&gt;The build passes.&lt;/p&gt;

&lt;p&gt;Then you run it, and you get a fatal error. &lt;code&gt;Call to undefined function getCollectionBySlug()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is the part that wastes your afternoon: &lt;strong&gt;the code was plausible.&lt;/strong&gt; A broken build is easy to catch. Plausible code pointing at an API that is not there gets past a fast read, because every individual token looks reasonable. Nothing about that template looks wrong until you run it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it happens
&lt;/h2&gt;

&lt;p&gt;The agent is not malfunctioning. It is working with what it has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Training data that is months or years old, so it recalls an API that used to exist.&lt;/li&gt;
&lt;li&gt;Documentation on a website that may describe a different version than the one installed.&lt;/li&gt;
&lt;li&gt;A file tree where your helper sits next to nine other files, none of which say "this one is the entry point."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Better prompts do not fix this. You can paste the docs into the context window, write a long instructions file, and describe the correct signature. It helps a little. It does not solve it, because the docs you pasted are a copy, and copies drift from what is actually installed.&lt;/p&gt;

&lt;p&gt;The real problem is the source of truth. The agent is guessing about a system that knows exactly what it is.&lt;/p&gt;

&lt;p&gt;Your installed app already knows its own schemas, its own routes, and which functions this version actually exposes. Nothing was asking it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: let the install describe itself
&lt;/h2&gt;

&lt;p&gt;Ship a small server inside the app that answers three questions. The protocol does not matter for this post; a plain HTTP endpoint works fine. What matters is that it is served &lt;strong&gt;from the running install&lt;/strong&gt;, so it cannot drift from reality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Schemas.&lt;/strong&gt; The real collections and fields on this site, not the generic example from the docs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Live content.&lt;/strong&gt; Real records, so the agent can look at what exists before it changes anything. This one prevents a whole category of mistake: inventing plausible field values for a record that was never queried.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Documentation for this version.&lt;/strong&gt; The docs ship with the install and are served from it, so an agent working on 3.6 reads 3.6's functions and field types. There is no gap between what the docs say and what the site does.&lt;/p&gt;

&lt;p&gt;The third one did the most work in my case. Once the agent could look up a real signature instead of recalling one, it stopped guessing. The reason is obvious in hindsight: your installed version is authoritative about your installed version, and a website is not.&lt;/p&gt;

&lt;p&gt;There is a second benefit that took me longer to notice. Customers do not always upgrade the moment you release. An agent that reads documentation from a marketing site will confidently use the feature you shipped last week, on the version they are still running. Documentation served from the install describes exactly what that site can do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writes should not get a special door
&lt;/h2&gt;

&lt;p&gt;Reading is the easy half. Letting an agent write is where people are right to be nervous.&lt;/p&gt;

&lt;p&gt;The rule I settled on: &lt;strong&gt;the agent gets no privileged path.&lt;/strong&gt; Every write goes through the same validation and fires the same events as a save from the admin interface. Permissions come from the same access groups that govern human editors. A token issued to the agent gets exactly the checks a signed-in user gets.&lt;/p&gt;

&lt;p&gt;The benefit is that "can the agent do this?" gets answered in one place, and that place already exists. You are not maintaining two authorization systems. You are not maintaining one authorization system and one audit trail that nobody reads.&lt;/p&gt;

&lt;p&gt;If your answer to "can the agent touch this?" lives somewhere other than your existing permission code, that is the actual bug. The fabricated function was a symptom.&lt;/p&gt;

&lt;h2&gt;
  
  
  A worked example
&lt;/h2&gt;

&lt;p&gt;Here is the shape of it, stripped down. The framework does not matter; the three questions do.&lt;/p&gt;

&lt;p&gt;Suppose your CMS has a &lt;code&gt;products&lt;/code&gt; collection. Each product has a &lt;code&gt;handle&lt;/code&gt;, a &lt;code&gt;title&lt;/code&gt;, and an optional &lt;code&gt;discount&lt;/code&gt; that is either null or a number between 0 and 100. On version 3.6 the accessor is &lt;code&gt;getCollectionBySlug()&lt;/code&gt;. On 3.5 it was &lt;code&gt;findCollection()&lt;/code&gt;. The rename happened two releases ago.&lt;/p&gt;

&lt;p&gt;An agent asked to render a discount banner will pick one. Which one it picks depends on what it has seen most often, and two releases apart is close enough that both appear in its training data. Nothing about the task signals that the version matters, so the agent has no reason to prefer the right one.&lt;/p&gt;

&lt;p&gt;Now give it a way to ask, and the version question answers itself:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /__self/versions
-&amp;gt; { "app": "acme-cms", "version": "3.6.4" }

GET /__self/schemas/products
-&amp;gt; { "handle": "string", "title": "string", "discount": "number or null, 0 to 100" }

GET /__self/docs/3.6/functions
-&amp;gt; { "getCollectionBySlug": { "params": ["slug"], "returns": "Collection" } }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The agent now has the right name, the right shape, and the constraint it would otherwise have invented. Nobody had to teach it the rename. The install already knew.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;discount&lt;/code&gt; line matters more than it looks. An agent that guesses the field will happily render &lt;code&gt;undefined%&lt;/code&gt; and hand you a bug report about the template. An agent that reads the nullable type will write the guard clause, because the type told it the guard is required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the read path is the easy half
&lt;/h2&gt;

&lt;p&gt;Reading is safe in a way writing is not, and it is worth being precise about why.&lt;/p&gt;

&lt;p&gt;A read cannot change state. The worst outcome of a wrong read is a wrong answer that a human immediately sees and corrects. Writes invert that: the wrong write looks exactly like the right one until the data is already changed, and by then the original value is gone.&lt;/p&gt;

&lt;p&gt;That asymmetry is the whole argument for keeping reads and writes on the same door. If the agent reads schemas through the install but writes through some convenience layer you added, the two paths drift. The read path is verified against reality on every call. The write path is verified against your assumptions, once, when you wrote it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this does not solve
&lt;/h2&gt;

&lt;p&gt;Worth being clear about, because these posts usually skip it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Logic bugs still need a human.&lt;/strong&gt; The agent knows the signature is real now. It does not know your business rule was wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency and boundary conditions still fail.&lt;/strong&gt; The same edge cases fail with a perfect API description.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You still owe a review pass.&lt;/strong&gt; Faster generation means more surface area, not less.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt injection remains open.&lt;/strong&gt; An agent that can read your content can be told by that content what to do. Self-description limits the blast radius; it does not remove it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The rule I would start with
&lt;/h2&gt;

&lt;p&gt;If your agent is working from documentation rather than from the running system, it is guessing with good manners. Make it ask the install instead.&lt;/p&gt;

&lt;p&gt;That single change removed the fabricated-API class of bug from my work entirely, and it took an afternoon. The part that took longer was accepting that the fix belonged in the app, not in the prompt.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>mcp</category>
      <category>codelint</category>
    </item>
    <item>
      <title>Your prompt won't fix AI-sounding writing. A lint rule will.</title>
      <dc:creator>Dekita</dc:creator>
      <pubDate>Thu, 01 Oct 2026 14:54:10 +0000</pubDate>
      <link>https://dev.to/dekita/your-prompt-wont-fix-ai-sounding-writing-a-lint-rule-will-1l0p</link>
      <guid>https://dev.to/dekita/your-prompt-wont-fix-ai-sounding-writing-a-lint-rule-will-1l0p</guid>
      <description>&lt;p&gt;I generate a fair amount of technical writing with models. For a long time I kept getting the same reaction from readers, and it took me too long to name it properly.&lt;/p&gt;

&lt;p&gt;The posts were not wrong. They were tiring.&lt;/p&gt;

&lt;p&gt;The failure had a shape. A section would open by announcing why the topic mattered before it said anything about the topic. A list would arrive as a column of bold labels, each followed by a colon and a restatement of the label. A section would end by hedging — "it is worth keeping in mind that..." — and commit to nothing.&lt;/p&gt;

&lt;p&gt;None of that is a factual error. All of it is a reason to stop reading at paragraph three.&lt;/p&gt;

&lt;h2&gt;
  
  
  The prompt approach, and why it kept slipping
&lt;/h2&gt;

&lt;p&gt;My first move was the obvious one. I described the problem to the model and asked it to stop.&lt;/p&gt;

&lt;p&gt;So the system prompt grew. Do not open with a definition. Vary sentence length. Skip the three-item lists. Do not end on a vague note.&lt;/p&gt;

&lt;p&gt;It held for a few days. Then it drifted back, and I could never tell exactly when.&lt;/p&gt;

&lt;p&gt;The reason is structural. A prompt is a request, and requests get weighed against everything else the model is doing, on every token. There is no moment where the instruction passes or fails. Nothing is measured, so nothing stays fixed. I was negotiating with a system that had no memory of the negotiation.&lt;/p&gt;

&lt;p&gt;There is a second problem, subtler. Style instructions in a prompt compete with each other. "Be concise" and "be thorough" both get weight. "Avoid lists" fights "be scannable." The model resolves that tension silently, differently on different days. You see the average of the conflict, never the conflict itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a linter does differently
&lt;/h2&gt;

&lt;p&gt;A linter flips the relationship. It does not ask for anything. It reads finished text and returns violations.&lt;/p&gt;

&lt;p&gt;The property that matters: &lt;strong&gt;a linter can go red.&lt;/strong&gt; That is a state a prompt cannot reach.&lt;/p&gt;

&lt;p&gt;For English prose the tool I would start with is Vale. Single binary, MIT licensed, understands Markdown well enough to skip code blocks, and runs offline with no account.&lt;/p&gt;

&lt;p&gt;Install it, then drop a &lt;code&gt;.vale.ini&lt;/code&gt; at the repo root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;StylesPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;.vale/styles&lt;/span&gt;
&lt;span class="py"&gt;MinAlertLevel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;warning&lt;/span&gt;

&lt;span class="nn"&gt;[*.md]&lt;/span&gt;
&lt;span class="py"&gt;BasedOnStyles&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;Vale, write-good&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it against a directory of drafts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vale articles/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want patterns aimed specifically at machine-written prose rather than general wordiness, there are community style packages for that. They tend to target the tells rather than any single word: participial padding ("...thereby ensuring that..."), contrastive negation used as a reflex ("not X, but Y"), and lead-ins that forecast a count ("there are three things to consider here") before delivering two.&lt;/p&gt;

&lt;p&gt;Which package you pick matters less than the fact that the rule is written down once, runs identically every time, and produces a number you can look at.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where you put the gate matters more than what is in it
&lt;/h2&gt;

&lt;p&gt;This is the part I got wrong first.&lt;/p&gt;

&lt;p&gt;I had a linter. I ran it manually, when I remembered to. Which meant I ran it maybe half the time.&lt;/p&gt;

&lt;p&gt;A gate that depends on you remembering to open it is not a gate. It is a suggestion with extra steps.&lt;/p&gt;

&lt;p&gt;So it goes on the publish path. For a GitHub-based flow, that is a workflow that runs on every push:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prose-gate&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vale&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;errata-ai/vale-action@reviewdog&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;all&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the text cannot reach a publishable state while it is failing.&lt;/p&gt;

&lt;p&gt;One caveat I learned the hard way, and it is worth stating plainly: &lt;strong&gt;on some platforms the CI job and the actual deploy are completely separate systems.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I run a similar gate for Japanese articles on Zenn. Zenn deploys by watching pushes to the branch directly. A red CI check does not stop it, does not delay it, and does not warn anyone. It is a separate set of lights on a separate wall.&lt;/p&gt;

&lt;p&gt;If your platform deploys outside CI, your CI gate is a notification, not a gate. The thing that actually blocks publication is the run that happens locally, before you commit. Check which one you have before you trust either.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not trust the style guide. Measure your corpus.
&lt;/h2&gt;

&lt;p&gt;Here is the finding that changed how I write rules.&lt;/p&gt;

&lt;p&gt;I was setting up a Japanese prose gate and hit a rule about spacing between full-width and half-width characters. The official style guide for Japanese technical writing says: do not add the space.&lt;/p&gt;

&lt;p&gt;Before writing that into the config, I pulled the ten most-liked Japanese posts on the platform and counted. 78% of them added the space.&lt;/p&gt;

&lt;p&gt;The official guide and the most-read actual writing disagreed, and not by a narrow margin.&lt;/p&gt;

&lt;p&gt;I turned the rule off and followed the corpus. Not because the guide was wrong in principle, but because a rule that fights the audience's reading habit is a rule that makes your writing feel foreign — which was the exact problem I was trying to solve.&lt;/p&gt;

&lt;p&gt;The general version: &lt;strong&gt;a style guide is a hypothesis about your readers. Your readers' own most-liked writing is evidence.&lt;/strong&gt; When the two conflict, look at the evidence, then decide. Do not let a document win an argument against data.&lt;/p&gt;

&lt;p&gt;This is also why I keep the rule config in the same repo as the articles. The rules are part of the content, and they should change when the corpus tells you to. A linter config that nobody revisits is a linter config that slowly becomes wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the linter cannot do
&lt;/h2&gt;

&lt;p&gt;I want to be careful here, because this is easy to oversell.&lt;/p&gt;

&lt;p&gt;A prose linter checks the &lt;em&gt;shape&lt;/em&gt; of your writing. It does not check whether the writing is &lt;em&gt;true&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Those are separate gates and you need both. The AI-content guidelines on both dev.to and Zenn are explicit that AI-assisted work has to be fact-checked before it goes out. No linter will do that for you. The smell of machine-written prose and the accuracy of a claim are independent axes, and passing one tells you nothing about the other.&lt;/p&gt;

&lt;p&gt;It also cannot tell you whether the piece should exist. A clean lint run on an article nobody needed is still an article nobody needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A prompt is a request. It drifts, and you cannot see it drift.&lt;/li&gt;
&lt;li&gt;A lint rule is a gate. It is deterministic and it can fail.&lt;/li&gt;
&lt;li&gt;Put the gate where publishing actually happens. If your platform deploys outside CI, that is your local pre-commit run.&lt;/li&gt;
&lt;li&gt;Write your rules from your own corpus. An official guide can be wrong about your readers.&lt;/li&gt;
&lt;li&gt;The gate covers style. Facts are a different gate, and you still need it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I still use prompts. I just stopped expecting them to be the thing that holds.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was written by a human and edited with AI assistance. The lint guidance described here was applied to this draft as well — the irony of shipping a piece about AI-sounding prose without running it through a gate was not lost on me.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>writing</category>
      <category>devtools</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
