Humanize Code: Remove AI Patterns

Remove signs of AI-generated code while preserving behavior. Use a test-guarded loop to detect and rewrite AI tells.

Sby Skills Guide Bot
DevelopmentIntermediate
007/26/2026
Claude Code
#humanize-code#ai-generated-code#code-quality#testing#refactoring

Recommended for


name: humanize-code version: 1.0.0 description: | Remove signs of AI-generated code from a codebase while preserving behavior. Use when editing or reviewing code to make it read like a human engineer on this team wrote it. Works through a test-guarded detect-modify-verify loop: pin down the functional contract and edge cases first, write or run tests as a safety net, then rewrite AI tells (redundant comments, emoji, verbose names, reflexive try/except, defensive null-guards, over-decomposition, speculative abstraction, dead code, non-idiomatic constructs, hallucinated dependencies) and re-verify after every change. The tests decide whether a defensive block is slop or a real safeguard. license: MIT compatibility: claude-code opencode allowed-tools:

  • Read
  • Write
  • Edit
  • Grep
  • Glob
  • Bash
  • AskUserQuestion

Humanize Code: Remove AI Code Patterns

You are an engineer cleaning up code that reads like a machine wrote it. AI-generated code has a fingerprint: redundant comments, decorative banners, emoji, verbose names, reflexive error handling, defensive guards in places that cannot fail, tiny single-use functions, speculative abstraction, and a suspiciously uniform shape. Your job is to make the code read like a competent human on this team wrote it, without changing what it does.

The Prime Directive

Never trade correctness for naturalness. This is the one rule that separates humanizing code from humanizing prose. A reworded sentence that loses a nuance is a small loss. A "cleaned up" function that drops a real edge case is a bug you shipped.

So this skill is built around a test-guarded loop, not a single rewrite pass. Every change must survive the test suite that pins down the behavior and the edge cases. If you cannot verify a change, you do not make it.

The goal is not to defeat an AI detector. The goal is code a senior engineer would actually write, read, and maintain on this specific codebase. Sometimes the most human thing is to leave well-written code alone.

Workflow

This is the core of the skill. Do not skip Phase 0. Do not rewrite without verifying.

Phase 0: Scope and Safety Net

Before touching anything, understand the target and build the net that catches mistakes.

  1. Map the functional contract. Identify the entry points, the public API, and what the code is supposed to do. What goes in, what comes out, what side effects happen.
  2. Enumerate the edge cases that must keep working. Empty or null inputs, malformed data, boundary values (zero, negative, max), error paths, missing config or environment variables, concurrency, large inputs. Write them down. These are the behaviors you are forbidden from breaking.
  3. Establish verification. Find how the project runs its tests (look for the test runner, CI config, package scripts). Run the existing suite and record a green baseline.
  4. Fill the gaps. If the existing tests do not cover the contract and the edge cases from steps 1-2, write tests that do, capturing the current correct behavior before you change anything. These tests are the arbiter for the rest of the loop. If the current behavior is itself buggy, note it and ask; do not silently "fix" it while humanizing.

If you cannot run tests at all (no runner, can't execute), say so explicitly and stop, or fall back to read-only review that flags patterns without rewriting. Do not rewrite blind and claim success.

The Loop: Detect, Modify, Verify

Work in small, reversible steps. One pattern or one region at a time, not a giant rewrite.

  1. Detect. Scan the code against the Pattern Catalog below. List the suspected AI-written regions and the specific tell for each. Look for clusters and style fractures, not isolated tells (see Detection Guidance). A single try/except means nothing; a block whose comment density, naming, and error handling all break from the surrounding file is a confession.
  2. Modify. Rewrite the flagged code into the style the surrounding codebase uses (see Style Calibration). Make behavior-preserving changes only. Do not add features, do not change the contract, do not "improve" adjacent code that wasn't flagged. Every changed line should trace to an AI tell you identified.
  3. Verify. Re-run the test suite. All functional and edge-case tests must stay green. If a test goes red, the change altered behavior. The most common cause: you removed a defensive block that was actually load-bearing. Either restore it, or relocate the check to the system boundary where it belongs (see Pattern 11). Never delete a failing test to make the loop pass.

Repeat until the exit criteria are met.

Exit Criteria

You are done when both hold:

  • No remaining AI tells from the catalog (or the remaining ones are justified false positives you can defend).
  • The full test suite is green: the original functionality is intact and every edge case from Phase 0 is handled correctly.

If you removed a guard and no test covered the case it protected, that is a gap in Phase 0. Add the test, then decide whether the guard was real.

Style Calibration

Do not impose a generic "human style." Match the codebase you are in. Before rewriting, read enough of the surrounding code to learn its conventions:

  • Naming. Short or descriptive? camelCase or snake_case? What do similar variables and functions get called here?
  • Comment density. Does this project comment heavily, sparsely, or not at all? Match it. A new function with a full docstring in a file that has zero docstrings is itself a tell.
  • Error handling idiom. Custom exception types? Result objects? Bare try/except? Match the project's existing approach instead of inventing one.
  • Type annotations. If the codebase annotates everything, keep annotations. If it doesn't, don't add them. Consistency is the signal, not the annotations themselves.
  • Test conventions. Match the existing test framework, file layout, and assertion style when you write the safety-net tests.

When there is no surrounding code to match (a brand-new file or project), fall back to the default: the minimum code that solves the problem, named and commented the way a pragmatic senior engineer would.

Pattern Catalog

The numbered patterns AI code falls into, with before/after. Examples span Python and JavaScript/TypeScript; the pattern applies regardless of language.

A. Comments and documentation

1. Redundant comments that restate the code

Problem: AI comments the obvious, narrating what the code plainly says. Humans comment the why, not the what.

Before:

# This function calculates the area of a circle
pi = 3.14159  # the value of pi
area = pi * (radius ** 2)  # area formula
return area  # return the area value

After:

return 3.14159 * radius ** 2

2. Over-structured docstrings and headers on everything

Problem: AI attaches a full doc block to every function and class regardless of need, including @author AI Assistant, @version 1.0.0, and a parameter list that just retypes the signature.

Before:

/**
 * Authenticates a user with the provided credentials
 * @param {string} username - The username of the user
 * @param {string} password - The password of the user
 * @returns {Promise<Object>} Authentication result containing token and user data
 * @throws {AuthenticationError} When credentials are invalid
 */
async authenticate(username, password) { ... }

After:

// Returns an auth token if the credentials are valid.
async authenticate(username, password) { ... }

Keep doc blocks where the project uses them on public API. Drop them on internal helpers where nothing else is documented.

3. Decorative comment dividers and banners

Problem: Section banners made of ===== or ***** and shouting capitalized labels. Humans use TODO and FIXME; they rarely draw ASCII rules.

Before:

# ============================================
# Helper Functions
# ============================================

After:

# (delete it; let the code structure speak, or use a plain one-line comment)

4. Emoji in code, comments, and docs

Problem: Emoji in headings, bullet points, log messages, and READMEs. Professional code documentation almost never uses them, and not every terminal renders them.

Before:

## Features ✨
- 🚀 Lightning-fast performance
- 🔒 Secure authentication

After:

## Features
- Fast request handling (p99 under 50ms in benchmarks)
- Token-based authentication

5. Exhaustive project-structure trees with file icons

Problem: A README that diagrams every folder and file with emoji icons. No human maintains this by hand.

Fix: Delete it, or keep a short tree of only the top-level directories that a newcomer actually needs, without icons.

6. Diff-anchored comments that narrate a change

Problem: Comments written as if narrating the commit ("this was added to replace the old approach") instead of describing the code as it is. They rot the moment the next change lands.

Before:

# This function was added to replace the previous loop that was O(n^2)

After:

# Uses a set for O(1) membership checks.

B. Naming

7. Over-descriptive, dictionary-length names

Problem: Names that read like a legal contract. Correct, but they fight the reader and break the line.

Before:

const userAuthenticationTokenExpirationTimestamp = Date.now() + 3600000;
const maximumNumberOfLoginAttemptsAllowed = 5;

After:

const tokenExpiry = Date.now() + 3600000;
const maxLoginAttempts = 5;

If you have to scroll horizontally to read a name, shorten it. Match the length and specificity of names already in the file.

8. Generic placeholder names and leftover scaffolding

Problem: data, result, temp, value where a domain name fits, plus literal placeholders like your_file.csv or your_api_key that were never replaced.

Before:

data = fetch("your_api_endpoint")
result = process(data)

After:

sessions = fetch(SESSIONS_URL)
active = filter_active(sessions)

C. Defensive code and error handling

This is the highest-risk category. The tests from Phase 0 are what tell you whether a guard is slop or a safeguard. Remove a guard, run the tests: if an edge-case test goes red, the guard was real. Restore it or move it to the boundary.

9. try/except (try/catch) around code that cannot fail

Problem: Reinforcement-trained models wrap blocks defensively to maximize run success, even where nothing can throw.

Before:

def add(a, b):
    try:
        return a + b
    except Exception as e:
        print(f"error: {e}")
        return None

After:

def add(a, b):
    return a + b

10. Over-broad exception catching that swallows errors

Problem: except Exception / catch (e) that hides the real failure and breaks fail-fast. A caller that gets None back cannot tell "not found" from "database down."

Before:

try:
    config = json.loads(raw)
except Exception:
    return None

After:

config = json.loads(raw)  # let a JSONDecodeError surface, or catch it specifically at the boundary

Catch the specific exception you can actually handle (json.JSONDecodeError), at the layer that can do something about it.

11. Reflexive null-guards and default-value fallbacks

Problem: ?., ||, ??, and silent defaults sprinkled through core logic. It looks safe, but it is local self-protection that masks broken contracts. Not every missing value should get a default; a missing required env var should usually fail loudly at startup, not quietly run on a fallback.

Before:

const port = Number(process.env.PORT?.trim() || 3000);
const apiKey = process.env.API_KEY?.trim() || "";

After:

const port = Number(process.env.PORT ?? 3000);     // a real default is fine
const apiKey = required(process.env.API_KEY);        // missing key must fail, not default to ""

The boundary principle: defensive validation belongs at system boundaries (HTTP params, form input, third-party API responses, env vars, CLI args). Inside trusted core logic it is noise. When you remove an internal guard, the right move is often to relocate the check to the boundary, not delete it outright.

12. Field-name guessing

Problem: Trying every plausible key because the model wasn't sure of the shape.

Before:

const label = item.title || item.name || item.label || "untitled";

After:

const label = item.title;  // confirm the actual field from the type or schema, then use it

13. Excessive type and argument validation in internal code

Problem: isinstance walls and assert-based argument checks on functions that are only ever called internally with known types.

Before:

def scale(values, factor):
    assert isinstance(values, list), "values must be a list"
    if not isinstance(factor, (int, float)):
        raise TypeError("factor must be a number")
    return [v * factor for v in values]

After:

def scale(values, factor):
    return [v * factor for v in values]

Trust internal callers. Validate at the boundary where untrusted input enters.

D. Structure and abstraction

14. Over-decomposition into tiny single-use functions

Problem: Two-line tasks split into their own functions, so the main flow is buried under one-call-site helpers.

Before:

def get_input(): return input("Name: ")
def format_name(n): return n.title()
def greet(n): print(f"Hello, {n}!")
greet(format_name(get_input()))

After:

name = input("Name: ").title()
print(f"Hello, {name}!")

Extract a function when it is reused, or when a name genuinely clarifies a complex step. Not by reflex.

15. Speculative abstraction and unrequested design patterns

Problem: Factories, strategies, interfaces, and config layers for single-use code. Often an interface wrapping a class that is itself already an abstraction, or a public method that only needs to be internal.

Fix: Inline the single implementation. Remove the interface that has one implementer. Drop "flexibility" and "configurability" nobody asked for. Add the abstraction when the second caller actually arrives.

16. Suspiciously uniform shape

Problem: Every function is 3-5 lines, every file is the same length, every block is perfectly separated, with no variation. Real code has range: a long function next to a one-liner, because the problem has range.

Fix: Don't pad or shrink to a target size. Let function length follow the work. This pattern is rarely fixed on its own; it is a signal that prompts you to look for the others.

17. Dead code, redundant helpers, and unused imports

Problem: AI generates variables, functions, and imports that are never used, plus duplicate logic blocks, because it cannot do whole-project reference analysis within its context window.

Fix: Remove imports, variables, and helpers that are genuinely unused. (Confirm with a search or a linter first.) Caution: only remove dead code that your understanding confirms is dead, or that the user flagged. Do not delete unfamiliar code just because you can't see its caller; it may be loaded dynamically or used elsewhere.

18. Non-idiomatic solutions

Problem: Code that works but ignores the language's idioms: a manual loop where a comprehension fits, a hand-rolled sort instead of the stdlib, .filter().map() chains where one pass reads better.

Before:

result = []
for x in items:
    if x > 0:
        result.append(x * 2)

After:

result = [x * 2 for x in items if x > 0]

Match what an experienced user of this language would reach for.

19. Over-engineered scaffolding for trivial scripts

Problem: A full argparse parser, config loader, and logging setup bolted onto a ten-line script.

Fix: Keep the scaffolding proportional to the task. A throwaway script does not need a CLI framework.

E. Project artifacts

20. Commit messages

Problem: Emoji-prefixed, rigidly perfect Conventional Commits whose body restates the diff line by line, on a one-line change.

Before:

✨ feat(auth): add login button

This commit adds a login button. It adds an onClick handler.
It adds a state variable. It adds a fetch call.

After:

Add login button to the header

Wires the existing /auth/login endpoint to the new header slot.

Describe why, not what the diff already shows. Match the repo's existing commit style.

21. PR descriptions

Problem: Every templated section (Summary, Changes, Test plan, Related) filled to the brim with generic phrasing ("improves maintainability," "enhances readability"), and a leftover "Generated with" signature.

Fix: Keep the description proportional to the change. Remove generated-by signatures. State what changed and why someone reviewing should care.

22. Unrequested README sections

Problem: AI adds documentation nobody asked for, often duplicating what the code already conveys.

Fix: Remove sections that restate the obvious or were never requested. Keep what a newcomer actually needs.

23. Hallucinated and undeclared dependencies (slopsquatting)

Problem: AI imports packages that do not exist, or that exist but were never added to the manifest. Beyond looking wrong, hallucinated package names are a real supply-chain risk: attackers register the invented names.

Fix: Verify every imported third-party package actually exists, is the one intended, and is declared in package.json / requirements.txt / the lockfile. Flag any import that resolves to nothing or isn't in the dependency list.

F. The meta-signal

24. Style fractures

Problem and fix: The single most reliable tell is not any one pattern; it is a region whose comment density, naming, error handling, or formatting suddenly diverges from the rest of the file. That seam is usually pasted AI output that was never adapted to the codebase. Treat a style fracture as the place to look hardest, then apply Style Calibration to make the region match its surroundings.

Detection Guidance

What NOT to flag (false positives)

Clean, careful human code hits many of these patterns legitimately. Over-correcting code is worse than over-correcting prose, because you can break it. Before rewriting, rule these out:

  • Defensive code at a real boundary. Validation of HTTP params, user input, third-party responses, env vars, and CLI args is correct and should stay. The tell is defense in internal code that cannot be reached with bad data, not defense as such.
  • Comments that explain a genuine why. A hidden constraint, a workaround for a specific bug, a non-obvious invariant. Keep these. Only cut comments that restate the code.
  • Type annotations in a typed codebase. If the project annotates everything, annotations are the convention, not a tell. Consistency is the signal.
  • Descriptive names that match the project. If the codebase favors descriptive names, a descriptive name is correct. Flag only names that are verbose relative to their neighbors.
  • Repetition in tests. Test code is legitimately uniform and repetitive. Do not "humanize" it into cleverness; clarity matters more there.
  • Framework-required boilerplate. Some structure is mandated by the framework, not chosen by a model.
  • Generated files. Do not humanize machine-generated code (protobuf output, migrations, lockfiles, bundled assets). Leave it alone.
  • A single isolated tell. One try/except, one longish name, one doc block. Means nothing on its own.

When in doubt, look for clusters. One emoji is nothing; emoji plus redundant comments plus reflexive try/except plus a style fracture is a confession.

Signs of human code (preserve these)

When you see these, lean toward leaving the code alone:

  • Variety in shape. A long function next to a tiny one, because the problem demanded it.
  • Scars. A TODO, a FIXME, a commented "this is a hack until X," a workaround with a linked issue. Models rarely leave these.
  • Domain-specific names that encode real business meaning, including imperfect abbreviations a human would actually type.
  • Idiomatic shortcuts that assume the reader knows the language.
  • Context-aware integration: code that uses the project's own exception types, logging, and dependency-injection patterns instead of generic equivalents.
  • Comments that argue or hedge about a tradeoff. Models default to clean, confident statements.

Process and Output

  1. Scope (Phase 0). State the functional contract, the edge cases that must hold, and how you will verify. Establish the green baseline; write safety-net tests if coverage is missing.
  2. Detect. List the flagged regions with the specific tell for each, noting clusters and style fractures.
  3. Modify and verify, in a loop. Make behavior-preserving rewrites one region at a time, re-running tests after each. Report any guard whose removal turned a test red, and what you did about it (restored, or relocated to the boundary).
  4. Confirm exit criteria. No remaining tells, full suite green, all Phase 0 edge cases covered.

Deliver: the detection list, the humanized code, the test results (baseline and final), and a short summary of what changed and which "defensive" blocks turned out to be load-bearing. If you could not run the tests, say so plainly rather than claiming the behavior is preserved.

Reference

The pattern catalog synthesizes observations from code-review write-ups on spotting AI-written code (emoji and over-structured comments, verbose naming, reflexive error handling, toy-problem structure), quantitative studies of LLM code quality (redundant and dead code, high cognitive complexity, the correctness-versus-security paradox), the defensive-coding critique (guards belong at boundaries, not in core logic), and the supply-chain work on hallucinated dependencies (slopsquatting). It is the code-focused companion to the prose "humanizer" skill, with one in, non-negotiable addition: code must keep working, so verification is built into the loop.

Related skills