Skip to content

Claude meet CUE: From Intuition to Precision with Self-Correcting AI#

AI coding assistants like Claude Code and Copilot are marvels of intuition. Born from the connectionist school of AI, they can generate code, configuration, and documentation with remarkable fluency. But as we discussed in our blog post about safer AI, this intuition comes with a challenge: a lack of precision.

How do we bridge the gap between an AI's probabilistic world and the deterministic systems it needs to control? How do we build systems that don't just hope for the bestโ€”avoiding the "YOLO-factor"โ€”but guarantee correctness?

This guide demonstrates how to solve one instance of this classic configuration correctness problem. We'll show you how to pair the intuitive power of Claude Code with the logical rigor of CUE to create robust, self-correcting workflows that are provably correct. While we use Claude Code as an example, all LLMs face this same precision problem.

This guide is a hands-on tutorial. You can find all the code to follow along and run the experiments yourself in this GitHub repository.


The Setup: A Simple Go Project#

We'll start with a basic "hello world" Go application. This gives us a concrete repository for our intuitive AI partner, Claude Code, to work with.

TERMINAL
$ go mod init example.com/claude
go: creating new go.mod: module example.com/claude
example/cmd/hello/main.go
package main

import "fmt"

func main() {
    fmt.Println("hello")
}

We can confirm everything is working as expected.

TERMINAL
$ go run ./cmd/hello
hello

Our goal is to ask Claude Code to create a GitHub Actions CI workflow for this project. This is a perfect task for an intuitive AI assistant.


Part 1: The "YOLO Factor" in Action#

A natural way to work with an AI assistant is to simply tell it what you want. A key to effective prompting is to be specific about the goal and the verification steps.

Here, we ask Claude Code not only to create the workflow but also to validate it using a specific cue command. This command leverages our curated CUE schema for GitHub Actions, adding a layer of logical verification to Claude Code's intuitive output.

> I want to host this repo on GitHub and use GitHub Actions for CI. Please
template an appropriate workflow and validate that workflow using `cue vet -c
-d '#Workflow' cue.dev/x/githubactions@latest workflow.yml`

Claude Code correctly identifies the project type and generates a valid workflow file.

โ— Bash(find . -maxdepth 2 -type f -name "*.json" -o -name "*.js" -o -name "*.ts" -o -name "*.py" -o -name "*.go" -o -name "*.rs" -o -name "Dockerfile" -o -name "*.yml"โ€ฆ)

...

โ— Read(go.mod)
    ย Read 4 lines (ctrl+r to expand)

โ— Bash(find . -name "*.go" | head -10)
    ย ./cmd/hello/main.go

โ— Read(cmd/hello/main.go)
    ย Read 8 lines (ctrl+r to expand)

...

โ— Perfect! This is a simple Go project with a basic "hello world" application.
  Let me update the todo and create an appropriate GitHub Actions workflow for Go
  projects.

โ— Bash(mkdir -p .github/workflows)

โ— Write(.github/workflows/ci.yml)
ย  ย  ย Wrote 72 lines to .github/workflows/ci.yml

โ— Now let me update the todo and validate the workflow using the cue command:

โ— Bash(cue vet -c -d '#Workflow' cue.dev/x/githubactions@latest .github/workflows/ci.yml)

โ— Perfect! I've created a comprehensive GitHub Actions CI workflow for your Go
  project at .github/workflows/ci.yml and validated it successfully using the CUE
  command.

The command succeeded! We now have a workflow file, and thanks to CUE, we have a high degree of confidence that it's structurally valid.

example/.github/workflows/ci.yml
name: CI
on:
  pull_request:
    branches: [ main ]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: Set up Go
      uses: actions/setup-go@v4
      with:
        go-version: '1.20'
    - name: Build
      run: go build -v ./...
    - name: Test
      run: go test -v ./...

The Fallacy of Conversational Memory#

This is great, but it requires us to remember to ask for validation in every prompt. It would be better if our AI partner could remember this rule. Let's try telling it to do so.

> I want you to always validate GitHub Actions workflow files when they are
  updated. Please remember this

Claude Code's response is reassuring, but it reveals the core challenge of working with purely intuitive systems.

โ— Understood. I'll always validate GitHub Actions workflow files using CUE
  after any updates. Let me validate the updated workflow now:

โ— Bash(cue vet -c -d '#Workflow' cue.dev/x/githubactions@latest .github/workflows/ci.yml)

โ— Validation successful - the updated workflow is valid.

"I'll always validate GitHub Actions workflow..." sounds promising, but is it true? Let's probe further.

> How and where is "I'll always validate GitHub Actions workflow files using
  CUE after any updates" remembered/configured?

Claude Code's response is honest and insightful.

โ— That instruction isn't actually remembered or configured anywhere in the
  system. I was responding conversationally to your request, but Claude Code
  doesn't have persistent memory between sessions or a way to configure custom
  validation rules.

  To make this behavior automatic, you could:

  1. Add it to a project README or CLAUDE.md file - I can read project
     documentation to understand conventions
  2. Create a git pre-commit hook that runs the CUE validation
  3. Add the validation step directly to your CI workflow so it runs
     automatically
  4. Use a Makefile or script that combines workflow updates with validation

This is a critical lesson and a perfect illustration of the "YOLO-factor." Conversational promises are not persistent configurations. Relying on the AI's "memory" is simply hoping for the best. While Claude Code cleverly suggests workarounds like a CLAUDE.md file, these are still fragile solutions that don't scale and lack the rigor needed for production systems.

In a production environment, these conversational promises are not reliable. We need to engineer precision.


Part 2: Engineering Precision with Programmatic Guardrails#

Instead of relying on conversational context, we can enforce our rules with deterministic, programmatic controls. This is how we move from hoping for correctness to guaranteeing it.

Claude Code hooks provide the perfect mechanism. Hooks are the composable guardrails that ensure every AI action is safe and correct.

The Hook Configuration#

First, we create a settings file that registers our hook. This configuration tells Claude Code: "After any tool is used (PostToolUse), run our CUE-powered validation script."

example/.claude/settings.json
{
    "hooks": {
        "PostToolUse": [
            {
                "matcher": "*",
                "hooks": [
                    {
                        "type": "command",
                        "command": ".claude/validate-github-actions.sh"
                    }
                ]
            }
        ]
    }
}

The Validation Logic: Shareable, Precise, and Declarative#

Our command points to a simple shell script that pipes Claude Code's action context to a CUE command.

Step 1: The Shell Script Wrapper#

example/.claude/validate-github-actions.sh
#!/bin/bash

if ! cat - | cue cmd validate >&2; then
    exit 2
fi
When this hook runs, Claude Code pipes a rich JSON object to the script. This object contains all the context: the tool that was used (e.g., Edit), the file that was changed, the session ID, and more.

Step 2: Modeling the Hook's Data with a CUE Schema#

To work with this data safely, we first define a CUE schema that describes its structure. This gives us type-safety and allows us to robustly parse the event data.

example/schema.cue
package example

// #hookInput defines the base schema of all hook inputs. Per the CUE that
// appears at https://docs.anthropic.com/en/docs/claude-code/hooks#hook-input
#hookInput: {
    session_id!:      string
    transcript_path!: string
    cwd!:             string
    permission_mode?: string
    hook_event_name!: string
}

// #toolBase is the base set of fields for a hook tool.
#toolBase: {
    #hookInput

    tool_name!:     string
    tool_input?:    _
    tool_response?: _
}

// #preToolUseInput is the base schema for PostToolUse hooks.
#preToolUseInput: {
    #toolBase

    hook_event_name!: "PreToolUse"
}

// #editTool is the schema for the Edit tool
#editTool: {
    #toolBase

    tool_name!: "Edit"
    tool_input?: {
        file_path!:  string
        old_string!: string
        new_string!: string
    }
    tool_response?: {
        filePath!:     string
        oldString!:    string
        newString!:    string
        originalFile!: string
        structuredPatch: [...]
        replaceAll?:   bool
        userModified?: bool
    }
}

// #postToolUseInput is the base schema for PostToolUse hooks.
#postToolUseInput: {
    #toolBase

    hook_event_name!: "PostToolUse"
    tool_response!:   _
}

#preEditToolUseInput: {
    #editTool
    #preToolUseInput
}

#postEditToolUseInput: {
    #editTool
    #postToolUseInput
}

Step 3: Implementing the Validation Logic in CUE#

The core logic is defined entirely in CUE. It performs three key tasks:

  1. It reads and parses the JSON event data from Claude Code using the schema we defined.
  2. It checks if the event was an Edit and if the file path was inside .github/workflows and ends with .yml.
  3. If both conditions are true, it executes cue vet using our curated GitHub Actions schema.

This declarative approach ensures our validation only runs exactly when and where we want it to.

The cue vet command in our script references a schema, cue.dev/x/githubactions@latest. This schema is part of a CUE module; a shareable, versioned unit of configuration logic. This principle of sharing and reuse is central to CUE. Just as we use this public module for the base schema, a company could create its own internal modules to define and distribute policies for runner sizes, approved actions, or security rules, ensuring consistency across all AI- and human-generated configurations.

example/validate_tool.cue
package example

import (

    "tool/os"
    "tool/file"
    "encoding/json"
    "strings"
    "path"

    "tool/exec"
)

_os: string @tag(os, var=os)

command: validate: {
    env: os.Getenv & {
        CLAUDE_PROJECT_DIR: string
    }

    read: file.Read & {
        $after:   env
        filename: "/dev/stdin"
        contents: string

        hookInput: {#hookInput, ...}
        hookInput: json.Unmarshal(contents)
    }

    if (read.hookInput & #postEditToolUseInput) != _|_ {
        let filepath = path.ToSlash(read.hookInput.tool_input.file_path, _os)
        let filename = path.Base(filepath, _os)
        let dir = path.Rel(env.CLAUDE_PROJECT_DIR, path.Dir(filepath), _os)

        if strings.HasSuffix(filename, ".yml") && dir == ".github/workflows" {
            validate: exec.Run & {
                cmd: ["cue", "vet", "-c", "-d=#Workflow", "cue.dev/x/githubactions@latest", filepath]
            }
        }
    }
}
We now have a robust, programmatic validation system. We've replaced a fragile conversational agreement with an unambiguous, automated, and shareable rule.


Part 3: The Neuro-Symbolic Payoff: A Self-Correcting Loop#

Let's see this synthesis of intuition and logic in action. To make things interesting, we'll manually introduce an error into our ci.yml file. The line something: else is not valid.

example/.github/workflows/ci.yml
name: CI
on:
  pull_request:
    branches: [ main ]

# sneak in an error!
something: else

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: Set up Go
      uses: actions/setup-go@v4
      with:
        go-version: '1.20'
    - name: Build
      run: go build -v ./...
    - name: Test
      run: go test -v ./...
Now, we'll ask Claude Code to make a perfectly reasonable change: add a gofmt step.

> Please add a gofmt step to ci.yml

Claude Code suggests the correct edit, but it is unaware of our hidden error. We accept the change. Immediately, our hook fires, CUE validates the file, and the precise, logical feedback is sent back to Claude Code.

โ— Update(.github/workflows/ci.yml)

โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ Edit file                                                                     โ”‚
โ”‚ โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚
โ”‚ โ”‚ .github/workflows/ci.yml                                                  โ”‚ โ”‚
โ”‚ โ”‚                                                                           โ”‚ โ”‚
โ”‚ โ”‚   15          uses: actions/setup-go@v4                                   โ”‚ โ”‚
โ”‚ โ”‚   16          with:                                                       โ”‚ โ”‚
โ”‚ โ”‚   17            go-version: '1.20'                                        โ”‚ โ”‚
โ”‚ โ”‚   18 +      - name: Check formatting                                      โ”‚ โ”‚
โ”‚ โ”‚   19 +        run: gofmt -s -d .                                          โ”‚ โ”‚
โ”‚ โ”‚   20        - name: Build                                                 โ”‚ โ”‚
โ”‚ โ”‚   21          run: go build -v ./...                                      โ”‚ โ”‚
โ”‚ โ”‚   22        - name: Test                                                  โ”‚ โ”‚
โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ”‚
โ”‚ Do you want to make this edit to ci.yml?                                      โ”‚
โ”‚ โฏ 1. Yes                                                                      โ”‚
โ”‚   2. Yes, allow all edits during this session (shift+tab)                     โ”‚
โ”‚   3. No, and tell Claude what to do differently (esc)                         โ”‚
โ”‚                                                                               โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

โ— Update(.github/workflows/ci.yml)
    ย Updated .github/workflows/ci.yml with 2 additions
       15          uses: actions/setup-go@v4
       16          with:
       17            go-version: '1.20'
       18 +      - name: Check formatting
       19 +        run: gofmt -s -d .
       20        - name: Build
       21          run: go build -v ./...
       22        - name: Test

> Edit operation feedback:
  - [.claude/validate-github-actions.sh]: something: field not allowed:
      .github/workflows/ci.yml:7:1
  task failed: command ["cue" "vet" "-c" "-d=#Workflow" "cue.dev/x/githubactions@latest" "/home/myitcv/tmp/claude_go_example/.github/workflows/ci.yml"]
  failed: exit status 1

This is the magic momentโ€”the neuro-symbolic payoff.

This rapid "shift left" feedback loop does more than just find errors. It enables faster iteration towards a correct solution and reduces inference cost. Because the feedback is immediate and precise, the AI doesn't waste time and tokens pursuing dead-end strategies or trying to debug a vague failure. It knows exactly what's wrong and can correct it in the next step.

Claude Code, the intuitive agent, sees the specific CUE error (something: field not allowed) and immediately takes action to fix the underlying problem, proposing a new edit to remove the offending line.

โ— Update(.github/workflows/ci.yml)

โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ Edit file                                                                     โ”‚
โ”‚ โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚
โ”‚ โ”‚ .github/workflows/ci.yml                                                  โ”‚ โ”‚
โ”‚ โ”‚                                                                           โ”‚ โ”‚
โ”‚ โ”‚   3      pull_request:                                                    โ”‚ โ”‚
โ”‚ โ”‚   4        branches: [ main ]                                             โ”‚ โ”‚
โ”‚ โ”‚   5                                                                       โ”‚ โ”‚
โ”‚ โ”‚   6 -  # sneak in an error!                                               โ”‚ โ”‚
โ”‚ โ”‚   7 -  something: else                                                    โ”‚ โ”‚
โ”‚ โ”‚   8 -                                                                     โ”‚ โ”‚
โ”‚ โ”‚   6    jobs:                                                              โ”‚ โ”‚
โ”‚ โ”‚   7      test:                                                            โ”‚ โ”‚
โ”‚ โ”‚   8        runs-on: ubuntu-latest                                         โ”‚ โ”‚
โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ”‚
โ”‚ Do you want to make this edit to ci.yml?                                      โ”‚
โ”‚ โฏ 1. Yes                                                                      โ”‚
โ”‚   2. Yes, allow all edits during this session (shift+tab)                     โ”‚
โ”‚   3. No, and tell Claude what to do differently (esc)                         โ”‚
โ”‚                                                                               โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

We accept the fix. The hook runs a final time, cue vet passes, and the process completes successfully. We are left with a final ci.yml that is provably correct.


Try it for yourself!#

Ready to move from theory to practice? All the code, CUE schemas, and hook configurations from this guide are available in this GitHub repository. Clone it today, fire up the claude assistant, and experience the power of building a self-correcting AI workflow for yourself!

Conclusion: From Guesswork to Confidence#

This simple example demonstrates how CUE can introduce precision to generative AI systems. However, the real power of CUE lies in its composability.

A real-world scenario would combine multiple sets of constraints and policies together. Company-specific policies could be enforced, such as:

  • Action Allow-listing: Define a list of approved GitHub Actions.
  • Security Policies: Enforce that all jobs run with the least privilege.
  • Resource Management: Validate that self-hosted runner labels conform to team budgets.

This composition happens directly in CUE. Instead of just validating against the base GitHub Actions schema, you would validate the AI's generated YAML with both the schema and your company's policies.

By combining the intuitive reasoning of Claude Code with the precise, shareable, and composable logic of CUE, we build sophisticated, policy-driven systems that are not only powerful but also provably safe, compliant, and correct. This is how we replace guesswork with confidence.

Intrigued? Want to know more?#

Get in touch with us to discuss the unique challenges in your architecture and explore how CUE can help you to make your AI integrations more reliable.