---
title: "Hardcoded Configuration"
type: "ai-smell"
slug: "hardcoded-configuration"
url: "http://localhost:3000/en/ai-smells/hardcoded-configuration.md"
category: "Maintenance"
description: "AI assistants bake environment-specific values — URLs, ports, paths, timeouts, keys, and magic numbers — directly into logic instead of reading them from config or environment, because a literal is the most probable next token and the model lacks awareness of your existing config layer."
---
# Hardcoded Configuration

> AI assistants bake environment-specific values — URLs, ports, paths, timeouts, keys, and magic numbers — directly into logic instead of reading them from config or environment, because a literal is the most probable next token and the model lacks awareness of your existing config layer.

## Signs and Symptoms

An AI assistant tends to emit a _plausible literal_ exactly where a reference belongs. The code runs in the demo, so the smell survives review unless you look for it.

Tell-tale signs:

* **Inline endpoints, ports, paths, and timeouts** sprinkled through business logic instead of a config module: `http://localhost:3000`, `/tmp/cache`, `5432`, `setTimeout(..., 30000)`.
* **Magic numbers/strings** with no named constant — retry counts, page sizes, rate limits, feature thresholds.
* **The same literal repeated across files** because the model regenerated it rather than importing an existing constant (the GitClear "copy/paste up, refactor down" pattern in miniature).
* **Hardcoded credentials / keys** — API tokens, encryption keys, or `Basic` auth strings pasted from training data.
* **An existing `config.ts` / `.env` / settings object that the new code ignores** entirely.
* **Environment-baked values**: a dev/staging URL or test account ID hard-wired into a path that ships to prod.

```ts
// 🚩 AI-generated: every knob is a literal, dev values baked in
export async function syncOrders() {
  const res = await fetch("https://api.staging.acme.dev/v1/orders", {
    headers: { Authorization: "Bearer sk_test_4eC39HqLyjWDarjtT1zdp7dc" },
    signal: AbortSignal.timeout(30000),
  });
  const orders = (await res.json()).slice(0, 50); // why 50?
  for (let i = 0; i < 3; i++) { /* retry 3x... why 3? */ }
}

```

The Authorization line is a gitleaks/Semgrep hit; the URL, `30000`, `50`, and `3` are magic-value smells; and `api.staging.acme.dev` is a dev value about to reach production.

## Reasons for the Problem

**Why models produce it**

* **A literal is the highest-probability next token.** Given `fetch(`, the cheapest completion that satisfies the immediate prompt is a concrete URL string, not `config.apiBaseUrl`. Resolving a _reference_ requires knowing a symbol exists elsewhere; emitting a _value_ requires nothing. Models optimize for a locally-plausible, runnable snippet.
* **Training corpus is self-contained snippets.** Tutorials, README examples, and Stack Overflow answers inline their values so they run standalone. The model learned that "good example code" hardcodes — the opposite of production hygiene.
* **No repo context / limited window.** The model usually hasn't read your `config/`, env schema, or constants file, so it can't reuse them. GitClear ties exactly this to the rise in duplication: assistants are "less likely to propose reusing a similar function… partly because of limited context size," with 5+-line duplicated blocks up \~8× in 2024 while refactored ("moved") lines fell from \~24% to \~9.5%.
* **Sycophancy / answer-the-prompt bias.** Asked to "add an orders sync," the model delivers something that works _now_; externalizing config is extra scaffolding it won't volunteer unless told to.
* **Training-cutoff staleness.** Baked-in literals are often _stale_ literals — old endpoints, deprecated API versions, default ports, or weak crypto constants (MD5, hardcoded keys). OX Security found 62% of AI-generated code ships with issues and attributes hardcoded keys/secrets/paths to patterns "learned from legacy codebases… \[with\] zero awareness that security standards evolved."

**Why it hurts**

* **Maintainability — Shotgun Surgery.** Changing one timeout or base URL means hunting every duplicated literal across the codebase; miss one and behavior silently diverges per call site.
* **Correctness across environments.** A staging URL or dev account ID hardwired into logic ships to prod; magic thresholds with no name drift out of sync with the values they should mirror.
* **Security.** Hardcoded tokens/keys are credential leaks — once committed they live in git history forever. The arXiv build-code study (2601.16839) found hardcoded paths/URLs and high-severity hardcoded credentials recurring in AI-generated build files.
* **Review load & tech debt.** Reviewers must verify _every_ literal's provenance. The AI-specific-smells work (arXiv 2509.20491) notes models handle "in-scope literals" fine but struggle with **value-sensitive** cases where correctness depends on thresholds that propagate through helpers — precisely the hardcoded values humans must now audit by hand. Per Stack Overflow's Q&A with Factory's Eno Reyes, baseline code quality is "the only signal" for whether agents accelerate or _decelerate_ a team — and hardcoded sprawl erodes exactly that baseline.

## Treatment

**Prompting / review tactics**

* **Point the model at your config surface:** "Read `src/config.ts` and `.env.example`; use `config.*` / `process.env` for every URL, port, timeout, and credential. Do not introduce literals." Models inline because they don't know the symbol exists — name it.
* **Forbid magic values explicitly:** "No magic numbers or strings — extract named constants." Then make it run `eslint --rule no-magic-numbers` and a secret scanner (gitleaks/Semgrep) and fix what they flag. Per Factory's guidance, wire linters/scanners into the loop so the agent self-corrects instead of leaning on human review.
* **Provide the env schema** (zod/envalid/`.env.example`) so the model has somewhere to put values rather than guessing them.
* **Grep new diffs** for `http`, `localhost`, IP literals, `Bearer `, and bare digits in call args before merging.

**The refactor** — name the classic moves:

* **Replace Magic Number with Symbolic Constant** for thresholds, counts, sizes.
* **Extract Function / Extract Config Module** to collect knobs into one typed, validated place.
* **Externalize to Environment** (12-factor) for anything that differs per environment or is secret; never commit secrets.

```ts
// config.ts — single, validated source of truth
import { z } from "zod";
const env = z.object({
  ORDERS_API_BASE_URL: z.string().url(),
  ORDERS_API_TOKEN: z.string().min(1),
  ORDERS_TIMEOUT_MS: z.coerce.number().default(30_000),
  ORDERS_PAGE_SIZE: z.coerce.number().default(50),
  ORDERS_MAX_RETRIES: z.coerce.number().default(3),
}).parse(process.env);
export const config = env;

```

```ts
// ✅ after: references, not literals — one place to change, secret out of source
import { config } from "./config";
export async function syncOrders() {
  const res = await fetch(`${config.ORDERS_API_BASE_URL}/v1/orders`, {
    headers: { Authorization: `Bearer ${config.ORDERS_API_TOKEN}` },
    signal: AbortSignal.timeout(config.ORDERS_TIMEOUT_MS),
  });
  const orders = (await res.json()).slice(0, config.ORDERS_PAGE_SIZE);
  for (let i = 0; i < config.ORDERS_MAX_RETRIES; i++) { /* ... */ }
}

```

If a secret already landed in a commit, rotate it — removing the line doesn't purge git history.

## Detected by

- **ESLint** `no-magic-numbers` — Disallow magic numbers (https://eslint.org/docs/latest/rules/no-magic-numbers)
- **SonarSource** `typescript:S109 / java:S109 — "Magic numbers should not be used"` — Magic numbers (https://rules.sonarsource.com/java/RSPEC-109/)
- **SonarSource** `java:S1075 — "URIs should not be hardcoded"` — Hardcoded URIs (https://rules.sonarsource.com/java/RSPEC-1075/)
- **SonarSource** `java:S2068 — "Hard-coded credentials are security-sensitive"` — Hard-coded credentials (https://rules.sonarsource.com/java/RSPEC-2068/)
- **Checkstyle** `MagicNumber` — Magic number check (https://checkstyle.org/checks/coding/magicnumber.html)
- **PMD** `AvoidUsingHardCodedIP` — Avoid hardcoded IP addresses (https://docs.pmd-code.org/latest/pmd_rules_java_bestpractices.html#avoidusinghardcodedip)
- **Bandit** `B105 hardcoded_password_string / B108 hardcoded_tmp_directory` — Hardcoded passwords / temp dirs (Python) (https://bandit.readthedocs.io/en/latest/plugins/b105_hardcoded_password_string.html)
- **gitleaks** `secret detection (e.g. generic-api-key, regex/entropy rules)` — Hardcoded secrets scanner (https://github.com/gitleaks/gitleaks)
- **Semgrep** `generic.secrets / hardcoded-credentials registry rules` — Hardcoded secrets/config rules (https://semgrep.dev/r?q=hardcoded)
- **eslint-plugin-no-secrets** `no-secrets/no-secrets` — High-entropy string (secret) detection (https://github.com/nickdeis/eslint-plugin-no-secrets)
