Skip to content

bug(infra): 32 unexpanded ${AUTOBOT_PROJECT_ROOT} placeholders in plain Python string constants, invisible to every flake8 code #14517

Description

@mrveiss

Problem

Found while fixing #14507, which named one site of this defect class. A sweep of the tree found 32 more.

A shell placeholder pasted into a plain Python string constant is never expanded — Python has no shell interpolation in string literals — so the value resolves to a literal directory name starting with ${AUTOBOT_PROJECT_ROOT:-. Reads silently miss, and mkdir(parents=True) creates a junk tree actually named after the expression. This is the defect class autobot_shared.paths.project_root() exists for (#13149).

#14405 / PR #14504 could only see the f-string form of this, because that produces an F821 for the undefined AUTOBOT_PROJECT_ROOT name. A plain literal is invisible to every flake8 code, so none of the sites below are reported by any lint that runs today, including the F821 audit added for this tree.

Scope

AST sweep for ast.Constant string values containing ${AUTOBOT_PROJECT_ROOT:

  • 26 sites across 21 files under autobot-infrastructure/shared/scripts/
  • 6 sites under autobot-infrastructure/shared/tests/

Deliberately excluded, not defects:

A representative sample (full list reproducible with the AST sweep):

autobot-infrastructure/shared/scripts/utilities/init_memory_graph_redis.py:56   .../logs/database/memory_graph_init.log
autobot-infrastructure/shared/scripts/optimize_agents.py:57                     .../.claude/agents
autobot-infrastructure/shared/scripts/analyze_duplicates.py:277                 .../reports/duplicate_analysis_results.json
autobot-infrastructure/shared/scripts/utilities/enhance_workflow_ui.py:770      .../autobot-vue/src/components/...
autobot-infrastructure/shared/tests/ci_cd_integration.py:224                    .../tests/results

The log-file and results-directory sites are the harmful half: they are write targets, so they create the junk tree rather than merely failing to read.

Fix

Replace each with autobot_shared.paths.project_root(), exactly as #14504 did at the reachable sites and #14507 did at the one plain-literal site it named. Do not open-code os.environ.get.

Do not add the guard first. A repo-wide lint banning the literal in Python string constants is the right end state, but it would need a baseline of 32 entries on day one, and this repo's experience with dormant exemption lists says a guard should land after the sites, not before it. Land the sweep, then the guard with no allowlist.

Related

Activity

  1. mrveiss commented on Aug 18, 2026

    @mrveiss
    OwnerAuthor

    Surfaced by the sweep in PR #14520 (#14507), which fixed the single site this issue's parent named.

  2. mrveiss commented on Aug 18, 2026

    @mrveiss
    OwnerAuthor

    Swept and guarded in #14545 (branch issue-14517).

    Measured count reconciles exactly with the issue: an AST sweep found 36 ast.Constant string hits, minus the deliberate compliance_manager.py literal (#13658) and the 3 fixture sites = 32 defects across 25 files — 26 under shared/scripts/ (21 files), 6 under shared/tests/. 6 further hits are docstrings, excluded structurally rather than by allowlist.

    Guard: tools/lint/check_no_shell_placeholder_paths.py, wired into code-quality with --audit, no baseline. It fails against the pre-fix tree (32 findings / 25 files), passes on the fixed tree, and fails on an empty enumeration rather than reading clean. The three deliberate sites are re-proved every run by path, anchor symbol and exact count, so a rename cannot strand an exemption.

    Two defects the repaired paths made visible, filed rather than absorbed:

    The knowledge_base import failures in several of these scripts are already covered by #14518 and were not re-filed.

  3. mrveiss commented on Aug 18, 2026

    @mrveiss
    OwnerAuthor

    Closed by PR #14545, squash-merged as d4b64475eb95 and verified in origin/Dev_new_gui. The guard is wired into code-quality.

    32 defects across 25 files, all resolved through autobot_shared.paths.project_root(). The count was re-derived by an independent AST sweep sharing no code with the shipped guard: 36 total non-docstring hits across 28 files, minus 4 exemptions. Both halves of the split reconcile — 26 sites / 21 files under shared/scripts/, 6 / 4 under shared/tests/. The 6 docstring hits were read individually and are prose, including autobot_shared/paths.py's own docstring about this defect class.

    What the fixes made reachable — the part worth reading

    These were never "the path was cosmetically wrong". Each named a directory that cannot exist, so the code failed early and reported success:

    • init_memory_graph_redis.py — the FileHandler ran at import, so the entire module was dead.
    • Three shared/tests/ suites — mkdir(exist_ok=True) would now raise FileNotFoundError, since the resolved parent genuinely does not exist where the junk-tree parent did. All take parents=True.
    • ci_cd_integration.py — a bad cwd= makes subprocess.run raise before the stage starts, which the except reported as an ordinary stage failure.
    • 9 KB/doc ingest scripts — every glob matched nothing and reported "Found 0 files" as success.

    Two structural catches beyond the sweep

    11 sites assigned to a local named project_root, which would have converted a silent empty result into an UnboundLocalError — renamed to root. Proved rather than assumed.

    run_code_analysis.py already bound a module-level project_root to autobot-infrastructure/shared — a different directory. It read as the canonical resolver and was not. Renamed to _SHARED_DIR with its value byte-identical and all 6 readers updated. A name that looks like the thing everyone else means is worse than an obviously broken one.

    The guard, and why its exemption mechanism matters

    No baseline — every defect fixed rather than grandfathered. Discrimination verified eleven ways, each executed: pre-fix FAIL (32/25), post-fix PASS, empty tree FAIL on a floor of 4000, an unparseable file fails rather than being skipped, and all four exemption-decay modes fail — exempt file moved, its anchor renamed, the entry gone dormant, and an exempt file growing a second literal that would otherwise inherit the exemption.

    That last set is what makes an exemption list a mechanism rather than a liability. This repo has been bitten by the opposite: a permission table holding a key for a renamed tool left the real tool undeclared at read level (#14494).

    The module self-guards by assembling its needle from fragments, so it needs no entry on its own list. And it is unaffected both when run from inside a path named .worktrees and when one is nested in the scanned tree — worth noting, because the author's own first sweep returned 0 hits for exactly that reason, and reported it rather than quietly fixing it. An empty enumeration reading as a clean tree is the failure this guard exists to refuse (#14484).

    Filed rather than absorbed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions