# How should generated code be handled for coding agents?

> Rule AR-23: Generated code is committed, or its generator is in the repo with the command to run it. Mark it, and tell agents to edit the source.

An agent that opens `client.py`, finds a bug, and fixes it in place has done the wrong thing if the file is regenerated from a schema tomorrow. It can't know that unless the file says so. Commit the output with a header that names the generator and the command, or keep the output out of the repo and put the generator and the command where the agent will read them. Then the agent edits the schema, runs the command, and reads the result.

## Why it happens
Generated files look like source. They have functions, imports and callers, and nothing in the language tells a reader which files are written by hand. Three things decide whether an agent handles them well:

- **Marking.** The Go project gives one documented convention: a line matching `^// Code generated .* DO NOT EDIT\.$` before the first non-comment, non-blank text in the file. Tools and people can find generated files by that line. Other languages have no single rule, so copy the shape: one comment at the top that says it's generated, by what, and how to regenerate.
- **Explicit runs.** The Go docs add that `go generate` is never run by `go build` or `go test`, and must be run explicitly. The same applies to most generators: if the command isn't written down, the next person (or agent) learns it by trial.
- **Visibility to code hosts.** GitHub reads a root-level `.gitattributes` with the same matching rules as `.gitignore`. Adding `linguist-generated` to a line (`search/index.json linguist-generated`) hides the file in diffs by default and leaves it out of the repository's language statistics. A minus sign (`bootstrap.min.css -linguist-generated`) unmarks a file that would be treated as generated. The linguist docs describe the effect as "excluded from stats, hidden in diffs", and say common generated files such as minified JavaScript are detected automatically. This attribute helps human reviewers. It doesn't stop an agent reading or editing the file.

**Committed or built on demand.** Both are workable, and they fail differently:
- *Committed output* is searchable and type-checkable in a fresh checkout. An agent finds the callers of a generated function. The risk is stale output, so CI should regenerate and fail on a diff.
- *Built on demand* keeps the repo small. In a fresh checkout the code doesn't exist, and every tool that reads files sees nothing until the generator has run. An agent has to run the command before anything resolves, so the command needs to be at the top of the instructions it reads.

**Where tools see it.** Excluding generated files from linting is normal: Ruff has `exclude`, `extend-exclude` and `lint.exclude`, and ESLint has `globalIgnores()`. Pyright's `exclude` marks paths as not part of the project, though the docs say files in excluded paths can still be analyzed if included source files import them, and its `ignore` setting suppresses diagnostics for the paths you list. So a generated module that your code imports stays visible to the type checker, and that's what you want.

## Bad and good
Take an API client generated from a schema file.

```python tab
# bad: committed output, no header, no command, patched by hand
# api/client.py
class UsersApi:
    def get_user(self, user_id: int) -> dict:      # someone fixed the return type here
        return self._get(f"/users/{user_id}")
```

```ts tab
// bad: committed output, no header, no command, patched by hand
// src/api/client.ts
export class UsersApi {
  getUser(id: number): Promise<any> {   // edited by hand last month
    return this.get(`/users/${id}`);
  }
}
```

The next regeneration overwrites the hand patch. An agent that fixes a bug here fixes it once, and the fix disappears.

```python tab
# good: header, source and command are all written down
# api/client.py
# Code generated by scripts/gen_client.py from api/schema.json. DO NOT EDIT.
# Regenerate: python scripts/gen_client.py
class UsersApi:
    def get_user(self, user_id: int) -> User: ...
```

```ts tab
// good: header, source and command are all written down
// src/api/client.ts
// Code generated by scripts/gen-client.mjs from api/schema.json. DO NOT EDIT.
// Regenerate: npm run gen:client
export class UsersApi {
  getUser(id: number): Promise<User> { /* ... */ }
}
```

Alongside it, a `.gitattributes` line for reviewers and the AGENTS.md block below for agents:

```gitattributes
api/client.py linguist-generated
src/api/client.ts linguist-generated
```

## Trade-offs and when to build on demand
- **Committed output costs diff noise.** Big regenerations make big diffs. `linguist-generated` collapses them on GitHub, and a CI check that regenerates and compares keeps them honest.
- **Build-time generation keeps the repo clean.** It suits very large outputs, such as bundled assets or large compiled schemas. It asks more of the instructions: the command has to be in AGENTS.md and the README.
- **A header is a convention, not a lock.** Nothing stops a hand edit. A CI regeneration check is the real guard.
- **Generated barrels** (see AR-02) are acceptable with star re-exports when the generator owns them and no one edits them by hand.
- **Mixing hand-written and generated code in one file** is the worst case for agents. Keep the generated part in its own file, and subclass or wrap it from hand-written code.
- **Large generated files** feed the size question in AR-24. Keep them out of the reading path when you can.

## Enforce it
| Tool | Setting | What it catches | Checked |
|---|---|---|---|
| git / GitHub | `.gitattributes` with `linguist-generated` | marks files as generated: hidden in diffs, left out of language stats | 2026-10-30 |
| ruff | `extend-exclude` or `lint.exclude` | keeps generated files out of lint runs (defaults are empty) | 2026-10-30 |
| eslint | `globalIgnores([...])` in `eslint.config.js` | keeps generated files out of lint runs | 2026-10-30 |
| pyright | `exclude` and `ignore` | `exclude` takes paths out of the project; `ignore` suppresses diagnostics for them | 2026-10-30 |
| CI | regenerate, then `git diff --exit-code` | committed output that has drifted from its source or been edited by hand | git-diff docs: `--exit-code` exits with 1 if there were differences and 0 if there were none |

In `pyproject.toml`:
```toml
[tool.ruff]
extend-exclude = ["api/client.py"]
```

In `eslint.config.js`:
```js
import { defineConfig, globalIgnores } from "eslint/config";
export default defineConfig([globalIgnores(["src/api/client.ts"])]);
```

## Rule block for AGENTS.md / CLAUDE.md
```markdown
## Generated code
- Files with a "Code generated ... DO NOT EDIT." header are outputs. Don't edit them.
  Change the source named in the header, then run the command named in the header.
- Generated files: api/client.py, src/api/client.ts. Sources: api/schema.json. Command: python scripts/gen_client.py
  and npm run gen:client.
- After running a generator, show the diff of the generated files in your final message.
- If a bug is in generated output and the generator can't fix it, say so in the final message rather than patching by hand.
```

## Edge cases
- **Lockfiles and snapshots** are generated, and they get a different treatment. Review them as data, and let the package manager write them.
- **Protobuf, OpenAPI and GraphQL outputs** commonly have a header already. Check that it names the command, and add the command in a wrapper script if it doesn't.
- **Generated tests** (snapshot files) are generated by the test runner. Update them with its documented flag, and read the diff.
- **Minified and bundled files** are detected by GitHub automatically, and they don't belong in agent read paths either.
- **The generator itself** is code agents edit. Put a test next to it that runs it on a small schema and compares the output to a fixture.

## What LLMGRAPH shows
LLMGRAPH lists callers of generated code like any other code and does not say the file is generated; the header and the AGENTS.md block carry that.

## References
- GitHub Docs, customizing how changed files appear on GitHub (`linguist-generated`): https://docs.github.com/en/repositories/working-with-files/managing-files/customizing-how-changed-files-appear-on-github
- Linguist, overrides: https://raw.githubusercontent.com/github-linguist/linguist/main/docs/overrides.md
- Go command documentation, generating Go files (header convention, explicit run): https://pkg.go.dev/cmd/go#hdr-Generate_Go_files_by_processing_source
- Ruff settings (`exclude`, `extend-exclude`, `lint.exclude`): https://docs.astral.sh/ruff/settings/
- ESLint, ignore files: https://eslint.org/docs/latest/use/configure/ignore
- Pyright configuration (`exclude`, `ignore`): https://raw.githubusercontent.com/microsoft/pyright/main/docs/configuration.md

## Changelog
- 2026-10-30: draft 1.
- 2026-10-30: docs checks resolved.

## Related

- [AR-02: Do barrel files hide calls from coding agents?](https://beta.llmgraph.in/agent-readable/ar-02.md)
- [AR-21: Do metaclasses hide code from coding agents?](https://beta.llmgraph.in/agent-readable/ar-21.md)
- [AR-26: How does an agent know an edit is finished?](https://beta.llmgraph.in/agent-readable/ar-26.md)

Last verified 2026-10-30
