Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 34

Debugging AI-Generated Code

Recommended resources

Hello! Welcome back. In our last lesson, you learned how to use Hugo's debugging functions like jsonify and warnf to inspect variables and enforce data quality. You now have the tools to look "under the hood."

Today, we'll put those tools to work. Our goal is to troubleshoot common issues in AI-generated code, such as variable scope or incorrect function usage. When "vibe coding," you'll find that AI tools are excellent at generating plausible-looking templates. However, they often fail on subtle but critical details of Hugo's logic. This lesson will teach you how to spot and fix these common failures, moving you from being a user of AI-generated code to its master.

The most frequent source of bugs in Hugo templates is a misunderstanding of scope and context. Let's dive into what that means and how to handle it.

The Core Problem: A Shifting Context

As we touched on previously, the . (the "dot") in a Hugo template represents the current context. It holds all the data available at that point. For a page, . contains the page's .Title, .Content, and .Params.

The problem is that this context isn't constant. It changes when you enter common control structures like loops (range) or conditional blocks (with). An AI might generate code that correctly accesses .Title on the page, but then uses the same .Title inside a loop where the context has changed, causing the build to fail or render incorrectly.

Your job as the developer is to track the dot. Let's explore how.


Understanding Scope: The Dot, The Dollar, and The range

The most common place you'll see the context change is inside a range loop. Imagine you ask an AI to create a list of blog posts on your homepage, and for each post, it should also display the homepage's title. It might generate something like this:

<!-- BROKEN CODE -->
<h1>{{ .Title }}</h1> <!-- This works, .Title is "My Awesome Website" -->

<ul>
  {{ range .Site.RegularPages }}
    <li>
      <a href="{{ .RelPermalink }}">{{ .Title }}</a>
      <!-- This fails. Inside the loop, '.' is a page in the loop. -->
      <!-- It doesn't have a '.Site' property. We want the HOMEPAGE title. -->
      <span>(From the site: {{ .Site.Title }})</span>
    </li>
  {{ end }}
</ul>

The code fails because inside the {{ range ... }} block, the . has been reassigned to the current page in the loop. That page object doesn't have a .Site property.

So, how do you access the original, top-level page context from within the loop? This is a fundamental concept in Hugo templating.

Hugo, the scope, the context and the dot

The article 'Hugo, the scope, the context and the dot' by Régis Philibert is one of the best explanations of this concept. It clarifies how the dot behaves and how to manage it.

Please read the first three sections: 'The context and the dot', the sub-sections on 'With' and 'Range', and 'The top level context 💲'. Pay close attention to how the dot's meaning changes inside with and range, and the role the $ symbol plays in solving this problem.

As you've just read, Hugo provides a simple and powerful solution: the dollar sign ($). The $ variable always holds the top-level context of the template.

Let's fix our broken example:

<!-- FIXED CODE -->
<h1>{{ .Title }}</h1> <!-- This still works, it's at the top level. -->

<ul>
  {{ range .Site.RegularPages }}
    <li>
      <a href="{{ .RelPermalink }}">{{ .Title }}</a> <!-- Here, .Title is the title of the post in the loop. -->
      <!-- We use '$.Site.Title' to reach out of the loop's context -->
      <!-- and grab the top-level .Site object's Title. -->
      <span>(From the site: {{ $.Site.Title }})</span>
    </li>
  {{ end }}
</ul>

This . vs $ distinction is the single most important concept for debugging Hugo templates. When your AI-generated code fails with a "can't evaluate field" error inside a loop, it's almost always because it should be using $ instead of ..


The Partial Problem: Passing Context Explicitly

The second most common AI error involves partials. Your experience with reusable components in various CMSs will make this concept intuitive. However, AIs often forget one key rule: partials do not inherit context automatically.

An AI might generate a call to a footer partial like this:
{{ partial "footer.html" }}

And inside layouts/partials/footer.html, it might try to use a variable:
<footer>&copy; {{ .Site.Copyright }}</footer>

This will fail. The partial was called with no context, so . is empty. You must explicitly pass the context to the partial.

Hugo, the scope, the context and the dot

Let's return to the same article, which also has an excellent section on how context works with partials.

Please read the sections 'Partials' and 'Top level $ from partial'. Focus on how to pass the dot to a partial, how to use dict to pass multiple values, and the crucial difference in how $ behaves inside a partial.

Here are the key takeaways from that reading:

  1. Pass the context: The simplest fix is to pass the current context to the partial:
    {{ partial "footer.html" . }}

  2. Pass multiple values with dict: For more complex components, like a card partial that needs data from a loop, you can use dict to create a custom context object.

    {{ range .Pages }}
      {{ partial "card.html" (dict "Title" .Title "Link" .RelPermalink "Summary" .Summary) }}
    {{ end }}
    

    Inside card.html, you would access these as .Title, .Link, and .Summary.

  3. Accessing page context from a partial: As the article points out, $ inside a partial refers to the context passed to the partial, not the top-level page context. If you need both, you must pass the page context into your dictionary.

    {{/* In list.html */}}
    {{ range .Pages }}
      {{/* Pass both the loop item and the top-level page context */}}
      {{ partial "card.html" (dict "Item" . "Page" $) }}
    {{ end }}
    
    {{/* In card.html */}}
    <div class="card">
      <h3><a href="{{ .Item.RelPermalink }}">{{ .Item.Title }}</a></h3>
      <p>This card is on the page titled: {{ .Page.Title }}</p>
    </div>
    
Test your understanding!

An AI generated the following code for a partial meant to display an author's bio. The front matter of the page has author: "John Doe". The code, however, produces a blank output.

{{/* In layouts/_default/single.html */}}
<h2>About the Author</h2>
{{ partial "author-bio.html" }}


{{/* In layouts/partials/author-bio.html */}}
{{ with .Params.author }}
  <p>Written by: {{ . }}</p>
{{ end }}

What are the two mistakes, and how would you fix them?

Show answer
  1. Mistake 1: The partial is called with no context. {{ partial "author-bio.html" }}.
  2. Mistake 2: Inside the partial, the {{ with .Params.author }} block tries to access .Params.author. Since the context . is empty, this fails silently.

Fix: You must pass the page context to the partial.

{{/* In layouts/_default/single.html */}}
<h2>About the Author</h2>
{{ partial "author-bio.html" . }}

Now, when author-bio.html is rendered, its . will be the page object, and {{ with .Params.author }} will work correctly.


A Systematic Debugging Workflow

Now, let's combine this knowledge with the tools from the last lesson into a concrete workflow for debugging AI-generated code.

Scenario: Your AI-generated template is failing or rendering blank sections.

  1. Isolate the Problem Area: Identify the range, with, or partial block where things are going wrong.
  2. Check the Context Before: Place {{ debug.Dump . | safeHTML }} or {{ . | jsonify (dict "indent" " ") | safeHTML }} wrapped in <pre> tags immediately before the problematic block. This shows you the context you think you have.
  3. Check the Context Inside: Place the same debug function inside the block. This shows you what the context has changed to.
  4. Compare and Correct: Look at the two outputs.
    • Is the variable you need missing from the inner context? You probably need to use $ (e.g., change .Site.Title to $.Site.Title).
    • Is the inner context completely empty? You probably forgot to pass context to a partial (change {{ partial "p.html" }} to {{ partial "p.html" . }}).

Troubleshooting Incorrect Function Usage

Another common AI error is using functions that don't exist or using real functions incorrectly.

  • Hallucinated Functions: An AI might confidently write {{ .Content | summarize 150 }}. Hugo has no summarize function. The build will fail with an error like "summarize" is not a defined function. Your first step is to search the official Hugo documentation for "summary" or "truncate" to find the correct function, which is .Summary or the truncate function.
  • Incorrect Function Arguments: An AI might use a real function with the wrong inputs. For instance, using where to filter pages: {{ where .Pages "featured" "true" }}. This looks right, but where expects a boolean, not a string. The correct call is {{ where .Pages "Params.featured" true }}.

In both cases, your debugging workflow is the same:

  1. Read the error message in the terminal carefully. It often points to the exact function that's failing.
  2. Use {{ debug.Dump . }} to inspect the variable you're trying to operate on. This confirms its data type (string, boolean, page object, etc.). You can find some of these debugging techniques in the article "Various ways to debug in Hugo" that we touched on previously.
  3. Consult the Hugo documentation for the function to confirm its correct syntax and required argument types.

Conclusion

You've now moved beyond simply prompting an AI and are equipped to be a true developer who can diagnose and fix the subtle but common errors that AI tools make. Understanding Hugo's context is the key that unlocks this capability.

Key Takeaways:

  • The context . changes inside range and with blocks.
  • Use $ to access the top-level page context from inside a nested block.
  • Partials must be given context explicitly, e.g., {{ partial "name.html" . }}.
  • Use dict to pass complex, custom context objects to partials.
  • When debugging, always inspect the context before and inside the problematic block to see how it changes.
  • When a function fails, check the Hugo documentation for the correct syntax and use debug.Dump to verify the data type of your variables.

Preview of the next lesson:
Now that you can write, generate, and debug template code with confidence, the next logical step is to manage your project's history and collaborate with others. In our next lesson, we will set up version control for the Hugo project using Git and push to a remote repository, laying the foundation for automated deployment.