# Why do star imports make my coding agent miss callers?

> Rule AR-01: Import names explicitly. Don't use from module import * in code that agents edit.

A star import binds every public name of another module without writing any of them down. A coding agent looking for who uses `parse_date` finds no `import parse_date` line in the files that call it. If the agent changes `parse_date`, those calls are easy to miss. Name each import, and every tool can trace the use back to its definition.

## Why it happens
The Python language reference defines exactly what the star form binds:
- With `__all__` defined, the star import binds exactly the names in `__all__`.
- Without `__all__`, it binds every name in the module that doesn't start with an underscore. That includes names the module itself imported from elsewhere.
- The star form is only allowed at module level; inside a function or class it's a `SyntaxError`.

The result is that the list of names arriving in a file is decided **in another file**, possibly by a computed `__all__` and possibly differently after the next edit there. A reader of the importing file, human or agent, can't see which names came from where. Tools must resolve the other module, then its `__all__`, before they can connect a call to its definition. When that resolution fails or is ambiguous, the call becomes a blind spot.

Two more effects make it worse in real code:
- **Silent shadowing.** `from a import *` followed by `from b import *` lets `b.format` replace `a.format` with no error. Code calls whichever came last.
- **Leaked imports.** Without `__all__`, a star import also re-exports what the source module imported (`os`, `json`, helper functions), so files start depending on names their author never meant to publish.

In TypeScript and JavaScript the closest equivalent is `export * from "./x"` in barrel files. See AR-02. (`import * as ns from "./x"` is different: every use is written `ns.name`, so tools can follow it.)

## Bad and good

```python
# bad: billing/report.py
from billing.dates import *
from billing.money import *

def monthly_total(rows):
    start = month_start(rows[0].when)    # from dates? money? both?
    return format_amount(sum_rows(rows, start))
```

```python
# good: billing/report.py
from billing.dates import month_start
from billing.money import format_amount, sum_rows

def monthly_total(rows):
    start = month_start(rows[0].when)
    return format_amount(sum_rows(rows, start))
```

The good version tells any reader where each name lives. Renaming `month_start` now has a findable import line in every file that uses it.

## Trade-offs and when a star import is acceptable
- **Interactive sessions and notebooks:** fine. Nobody edits them through an agent workflow.
- **A package `__init__.py` that deliberately re-exports a submodule's API:** acceptable only if the submodule defines `__all__`. Even then, an explicit list in `__init__.py` is clearer (see AR-02).
- **Generated constant modules** (thousands of constants): prefer `import constants as c` and `c.NAME`. It's as short at the call site and fully traceable.

## Enforce it
| Tool | Setting | What it catches | Checked |
|---|---|---|---|
| ruff | `F403` (undefined-local-with-import-star) | any `from x import *` | 2026-10-30 |
| ruff | `F405` (undefined-local-with-import-star-usage) | names that may come from a star import | 2026-10-30 |
| pyright | `reportWildcardImportFromLibrary` (on in strict mode) | star imports from external libraries | 2026-10-30 |

In `pyproject.toml`:
```toml
[tool.ruff.lint]
extend-select = ["F403", "F405"]
```

## Rule block for AGENTS.md / CLAUDE.md
```markdown
- Import names explicitly (`from pkg.mod import name`). Never add `from x import *`.
- When you meet an existing star import in a file you edit, replace it with explicit imports
  of the names the file uses, and run the linter to confirm nothing else depended on it.
```

## Edge cases
- **`__all__` built at runtime** (`__all__ = [n for n in dir() if ...]`): the public names then depend on execution, so no static tool can list them. Write the list out.
- **A name defined in both modules:** the later star import wins without warning. This is the source of chimera CH-10.
- **Relative star imports in packages** (`from .models import *`): same rule. The fix is the same.

## What LLMGRAPH shows
For calls through the star import, LLMGRAPH's type checker still links each call (certain); grep and readers are the ones who lose it.

## References
- Python Language Reference, "The import statement" (wildcard form and `__all__`): https://docs.python.org/3/reference/simple_stmts.html#the-import-statement
- PEP 8, "Imports": wildcard imports should be avoided.
- Ruff rules F403 and F405: https://docs.astral.sh/ruff/rules/undefined-local-with-import-star/ · https://docs.astral.sh/ruff/rules/undefined-local-with-import-star-usage/
- Pyright configuration: https://github.com/microsoft/pyright/blob/main/docs/configuration.md

## Changelog
- 2026-10-30: draft 1.

## Related

- [AR-02: Do barrel files hide calls from coding agents?](https://beta.llmgraph.in/agent-readable/ar-02.md)
- [AR-03: What happens when two functions share a name?](https://beta.llmgraph.in/agent-readable/ar-03.md)
- [AR-04: Do import aliases confuse coding agents?](https://beta.llmgraph.in/agent-readable/ar-04.md)

Last verified 2026-10-30
