Graphify-Labs/graphify · error · AttributeError

module {__name__!r} has no attribute {name!r}

Error message

module {__name__!r} has no attribute {name!r}

What it means

A deliberate shrink-guard (#479) in the graphify export step (tools/skillgen/expected/graphify__skill-vscode.md:433). graphify.export.to_json(G, communities, 'graphify-out/graph.json') compares the new graph's node count against the existing graph.json and returns False (writing nothing) when the new graph is smaller. The script then exits 1 so that GRAPH_REPORT.md and .graphify_analysis.json are never written for a graph that graph.json does not actually contain (#1392).

Source

Thrown at graphify/__main__.py:155

_ALWAYS_ON_ALIASES = {
    "_CLAUDE_MD_SECTION": "claude-md",
    "_AGENTS_MD_SECTION": "agents-md",
    "_GEMINI_MD_SECTION": "gemini-md",
    "_VSCODE_INSTRUCTIONS_SECTION": "vscode-instructions",
    "_ANTIGRAVITY_RULES": "antigravity-rules",
    "_KIRO_STEERING": "kiro-steering",
}


def __getattr__(name: str) -> str:
    # PEP 562: lazily resolve the legacy always-on section constants for external
    # importers (e.g. the install-string tests). In-module code calls _always_on()
    # directly; nothing is read at import time, so a missing block can no longer
    # brick the CLI on `import graphify.__main__` (#1121 follow-up).
    base = _ALWAYS_ON_ALIASES.get(name)
    if base is not None:
        return _always_on(base)
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")








def _check_skill_version(skill_dst: Path) -> None:
    """Warn if the installed skill is from an older graphify version."""
    version_file = skill_dst.parent / ".graphify_version"
    try:
        if not version_file.exists():
            return
    except OSError:
        return
    try:
        skill_exists = skill_dst.exists()

View on GitHub (pinned to 7fe58b0b0f)

Solutions

  1. If the shrink is intentional (files were deleted from the corpus), re-run a full build with --force as the error message instructs, so graph.json is overwritten regardless of node count.
  2. If the shrink is NOT intentional, inspect why extraction produced fewer nodes: skipped files, changed skip rules, wrong INPUT_PATH, or a partial extraction.
  3. Compare node counts before deciding: read the 'nodes' length in the existing graphify-out/graph.json versus G.number_of_nodes() printed by the pipeline.
  4. If graphify-out is stale from another project, delete graphify-out/ entirely and rebuild from scratch.

Example fix

# before: incremental update refuses to shrink
$(cat graphify-out/.graphify_python) graphify update .
# ERROR: refused to shrink ... (#479)

# after: intentional shrink -> force full rebuild
$(cat graphify-out/.graphify_python) graphify build --force .
Defensive patterns

Strategy: validation

Validate before calling

import json
from pathlib import Path

existing = Path('graphify-out/graph.json')
if existing.exists():
    old_n = len(json.loads(existing.read_text(encoding='utf-8')).get('nodes', []))
    extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding='utf-8'))
    new_n = len(extract.get('nodes') or [])
    if new_n < old_n:
        print(f'planned shrink {old_n} -> {new_n}: pass --force if intentional, else investigate skipped files')

Prevention

When it happens

Trigger: Calling to_json() on a graph whose node count is lower than the node count stored in the already-existing graphify-out/graph.json. Typical trigger: an incremental --update re-extract that found fewer files (or skipped some) than the original full build, or the corpus genuinely shrank because files were deleted.

Common situations: Running graphify update after deleting source files; partial re-extraction where some files were skipped; stale graph.json from a previous larger corpus left in graphify-out/; switching INPUT_PATH to a smaller subtree without clearing the old output.

Related errors


AI-assisted analysis of Graphify-Labs/graphify@7fe58b0b0f (2026-08-14). Data as JSON: /api/errors/449516cead7f134d. Report an issue: GitHub.