Half renamed codebase
Description
A specialization of graduation-promotion where the lagging-rename pattern is itself diagnostic: the project’s new structural shape is visible in the code, but the old name is still in the directory tree / config keys / function signatures / docs. The mismatch surfaces an UNABSORBED mental model — the maintainer’s reflexes haven’t caught up to where the code has already moved. The diagnostic move: when you notice you’re working around an old name, ask “what mental model would I be using if the name matched the code?” The gap usually points to a structural simplification or a missing abstraction. Structural shape:- Project starts with a name N₀ encoding mental-model M₀.
- Iteration produces a structurally different system better described by M₁.
- Code reflects M₁ in shape; surfaces (file names, identifiers, docs) still reflect N₀.
- Working on the codebase requires translation between the old vocabulary and the new reality on every read.
- The translation cost is the load-bearing signal that the rename is overdue.
Triggers
Surface this concept when reading code and noticing:- A directory tree, config key, identifier, or doc heading whose name no longer describes what’s underneath, and the mental translation happens on every read.
- A migration that “completed” at the structural level but where the old name still ships everywhere — package names, env-var prefixes, OAuth scopes, branch references in CI scripts.
- A name that requires explanatory comments to use correctly (“this is called X but really represents Y now”).
- Two reviewers disagreeing on what a name means because one is reading it through the M0 lens and the other through the M1 lens.
Exclusions
The diagnostic question category-mismatches in three situations:- Aesthetic-only renames. A product rebrand where the old name lingers in surfaces but the underlying architecture and vocabulary are unchanged. The lag is decorative, not a structural-outgrowth signal — there’s no M0 → M1 shift to translate against.
- Load-bearing back-compat aliases. The old name persists because it IS the public API contract that callers depend on; the new name is an internal-only refactor. The persistence is a feature, not lag — the new name lives in the implementation, the old name lives in the interface, and both are correct at their respective layers.
- Pipeline-locked vocabulary. Mismatches caused by generated stubs, schema files, or external systems where the local code can’t unilaterally rename without coordinated upstream change (e.g., protobuf field names encoding wire compatibility). The lag reflects coupling cost, not an unabsorbed mental model the maintainer could address with a local refactor.
Structure
Relationships
Examples
Git `master` → `main` default-branch rename (initiated industry-wide 2020; Git 2.28 added `init.defaultBranch` config in July 2020 to warn users to set a preference) · computer-science
Git `master` → `main` default-branch rename (initiated industry-wide 2020; Git 2.28 added `init.defaultBranch` config in July 2020 to warn users to set a preference) · computer-science
master to main, the rename completed at the surface of new projects almost immediately — GitHub, GitLab, and CircleCI defaulted new repositories to main, and developer tutorials updated quickly. But the lagging-name pattern persists years later in tooling that hardcoded the old name before the rename: GitHub Actions workflows pinned to uses: actions/checkout@master break silently when an action repository renames its branch, because GitHub Actions does not follow branch redirects for the uses: reference. CI scripts with hardcoded git push origin master or paths like ./dist/master.zip continue to ship in older container images and enterprise monorepos that haven’t been refactored.Inference: The translation cost is concentrated at the seam between the old code and any newer system it touches. A developer onboarding to a codebase that says master everywhere has to maintain a running mental translation against documentation, CI defaults, and colleagues’ git commands that all assume main. The persistence isn’t merely a rename TODO — the surface name encodes an obsolete mental model of “which branch is canonical,” and reading the code requires translating into the current one on every encounter.Python 2 → Python 3 string-handling migration (Python 3.0 released December 2008; Python 2 sunset January 2020) · computer-science
Python 2 → Python 3 string-handling migration (Python 3.0 released December 2008; Python 2 sunset January 2020) · computer-science
str was bytes by default; text was the exception (unicode). Python 3 inverts this: str is text; bytes are the exception (bytes). Code carrying the Python 2 mental model often shows up as defensive .encode('utf-8') and .decode('utf-8') calls scattered through functions that never touch a network or file boundary, type annotations that conflate str and bytes, or variable names like string_data holding bytes (or vice versa). The functions work; the naming reflects a world where “string means bytes” was correct.The translation cost surfaces at every type boundary. A reader has to ask “does str here mean Python 3 str, or does this developer mean what Python 2 called str?” — and the answer often requires reading the function body to find out. The surface vocabulary still encodes M0 (bytes-as-default); the code’s correctness depends on M1 (text-as-default). Resolving the lag is not a mechanical rename; it requires re-deriving which values are actually text and which are actually bytes.jQuery → React/Vue component-model migration (React released 2013; large-scale migrations from jQuery codebases through the late 2010s and early 2020s) · computer-science
jQuery → React/Vue component-model migration (React released 2013; large-scale migrations from jQuery codebases through the late 2010s and early 2020s) · computer-science
$ (a jQuery convention indicating a jQuery-wrapped DOM element) persist in React components where the value is no longer a wrapped DOM node but a ref or a piece of state. “Selector” identifiers — selectFooBar, getElementSelector, fooSelector — appear in code that no longer queries the DOM imperatively but composes declaratively-rendered components. Helper functions named bindEvents or attachHandlers remain in code where event binding is a prop on a JSX element, not a separate lifecycle step.The mental-model gap is that jQuery’s model was the DOM as a queryable, mutable surface; React’s model is the DOM as a derived output of declarative component state. The lagging vocabulary keeps inviting the reader to ask “where’s the selector that finds this element?” — a question the code’s actual shape has rendered nonsensical, because the element exists only as a function of state and is never queried at all. Reading the code requires translating each $el or selector identifier into “the state value that determines whether this element renders,” every time.Twitter → X rebrand (July 2023); developer-ecosystem terminology lag observable through 2026 · computer-science
Twitter → X rebrand (July 2023); developer-ecosystem terminology lag observable through 2026 · computer-science
api.x.com and the official first-party SDKs ship under an “XDK” brand, yet the most widely-used community libraries — tweepy in Python, twitter-api-v2 in TypeScript — still carry the old name in their package identifiers, README headers, and import paths. The api.twitter.com domain itself persists as a legacy redirect, and environment-variable conventions in deployed codebases still use the TWITTER_API_KEY / TWITTER_BEARER_TOKEN prefix even though new documentation has moved to X_API_KEY.The translation cost shows up on every read: a developer touching an OAuth flow has to mentally translate “Twitter app credentials” into “X developer-portal app credentials,” and a debugger watching outbound HTTP traffic sees api.twitter.com requests succeed via redirect to a service whose canonical name is now X. The old name describes the company that no longer exists; the code’s behavior is governed by an organization with a different mental model of what the platform is.