Skip to content

tech-debt(infrastructure): no canonical project-root resolution — 60 unexpanded Python literals + 34 shell scripts defaulting to the live install #13149

Description

@mrveiss

Discovered while fixing #13092 (run_agent.sh targeting the live install). That fix repaired one script; the same pattern is spread across the repo in two distinct failure modes, and there is no canonical way to resolve the project root in either language.

Class A — Python: the placeholder is inside a string literal and never expands (60 occurrences, 40 files)

# autobot-backend/monitoring/monitoring_and_alerts_test.py:159
self.results_dir = Path("${AUTOBOT_PROJECT_ROOT:-/opt/autobot/code_source}/tests/results")
self.results_dir.mkdir(exist_ok=True)

Python does not expand shell syntax inside string literals:

$ python3 -c 'from pathlib import Path; p=Path("${AUTOBOT_PROJECT_ROOT:-/opt/autobot/code_source}/tests/results"); print(p); print(p.exists())'
${AUTOBOT_PROJECT_ROOT:-/opt/autobot/code_source}/tests/results
False

The path is a literal directory name. Reads miss, and mkdir either fails on the missing parent or creates a junk tree named ${AUTOBOT_PROJECT_ROOT:-. Nothing in the repo calls os.path.expandvars — verified: git grep expandvars -- '*.py' returns nothing.

This is the same defect as #4945, in call sites that issue did not cover. #4945 was scoped to sys.path.append/insert and is genuinely complete — git grep 'sys\.path\.\(append\|insert\).*AUTOBOT_PROJECT_ROOT:-' returns 0. The remaining 60 are Path(...), log paths, config paths, and .env lookups that were never in scope. Not a premature closure — an uncovered sibling.

22 of the 40 files are non-test tooling, e.g.:

  • autobot-infrastructure/shared/scripts/populate_knowledge_base.py
  • autobot-infrastructure/shared/scripts/run_code_analysis.py
  • autobot-infrastructure/shared/scripts/analyze_duplicates.py
  • autobot-infrastructure/shared/scripts/ai-ml/optimize_llm_models.py
  • autobot-infrastructure/shared/scripts/utilities/migrate_consolidated_config.py

Class B — Shell: expands correctly, but defaults to the live install (35 scripts)

BASE_DIR="${AUTOBOT_PROJECT_ROOT:-/opt/autobot/code_source}"

Here the expansion works, so the failure is subtler: with no env set, a script run from a checkout or a git worktree operates on /opt/autobot/ instead of its own tree. That is what #13092 was — run_agent.sh rm -rf'd the live install's frontend node_modules from any checkout. run_agent.sh is fixed; 34 other scripts still carry it, including:

  • autobot-slm-backend/ansible/deploy-native.sh, deploy-hybrid.sh
  • autobot-infrastructure/shared/scripts/cleanup-disk-space.sh
  • autobot-infrastructure/shared/scripts/rotate_logs.sh
  • autobot-infrastructure/shared/scripts/bulletproof-frontend/zero-downtime-update.sh
  • scripts/service-auth/emergency-rollback.sh
  • sync-frontend.sh

Scripts that delete or rotate files are the risk here, for the same reason #13092 was.

Root cause: no canonical project-root resolution

Both classes exist because each call site re-invents root resolution by pasting a literal. There is no shared helper in either language, so a fix applied in one place (as in #13092) does not propagate.

Proposed consolidation

  1. Shell: a sourceable scripts/lib/project_root.sh exporting PROJECT_ROOT, resolved from BASH_SOURCE with AUTOBOT_PROJECT_ROOT taking precedence — the pattern merged in fix(scripts): resolve run_agent.sh paths from the script, not the live install (#13092) #13093. Scripts source it instead of pasting the default.
  2. Python: a single accessor in autobot_shared (e.g. autobot_shared.paths.project_root()) reading AUTOBOT_PROJECT_ROOT and falling back to a repo-root walk from __file__. Replace all 60 literals with it.
  3. Guard: a CI check (or pre-commit hook) failing on any new ${AUTOBOT_PROJECT_ROOT:- inside a Python string literal — the mechanism that let bug(infrastructure): 99 scripts use sys.path.append("${AUTOBOT_PROJECT_ROOT...}") — Python never expands shell variable syntax #4945 regress into 60 new sites.

Step 3 matters most: without it this recurs, which is exactly what happened between #4945 and now.

Acceptance

  • git grep 'AUTOBOT_PROJECT_ROOT:-' -- '*.py' returns 0.
  • Shell scripts resolve their root from a shared helper; none embeds the live-install path as a default.
  • A gate rejects reintroduction.
  • Config/YAML occurrences (logging.yml, promtail-config.yml, compliance.yaml, threat_detection.yaml) audited separately — those may legitimately rely on the consuming process expanding env vars; confirm per consumer rather than bulk-editing.

Suggested decomposition

  • Task 1: Python accessor in autobot_shared + CI gate
  • Task 2: migrate the 22 non-test Python tooling files
  • Task 3: migrate the 18 Python test files
  • Task 4: shell helper + migrate the 34 remaining scripts (deletion/rotation scripts first)
  • Task 5: audit the YAML/config occurrences per consumer

Activity

  1. mrveiss commented on Jul 31, 2026

    @mrveiss
    OwnerAuthor

    Related: #13151. Same shape — a documented rule with no gate decaying after a correctly-scoped fix. #3391 (encoding), #4945 (sys.path) and #13092 (run_agent.sh) each fixed their stated scope and each lacked a guard, so the pattern reappeared elsewhere. Both issues propose an AST-based pre-commit hook following the existing no-utcnow-isoformat / no-literal-ttl-seconds idiom.

  2. mrveiss commented on Aug 1, 2026

    @mrveiss
    OwnerAuthor

    Related: #13200. Same drift shape as this issue — a correct gate plus legacy call sites it never inspects, because pre-commit only checks changed files. #13200 also covers the missing test coverage on 3 of the 4 local checkers and a shared harness to remove their duplicated scaffolding.

  3. mrveiss commented on Aug 2, 2026

    @mrveiss
    OwnerAuthor

    Generated-artifact face of this filed as #13298: the same unexpanded path literals are baked into api.ts as OpenAPI @default values, so verify-generated-types only reproduces on the CI runner and the live install path is already committed in that file. Found while regenerating types in PR #13297.

  4. mrveiss commented on Aug 4, 2026

    @mrveiss
    OwnerAuthor

    Concrete data point from #13551 (PR forthcoming), for Task 1 of the decomposition above.

    There are two hand-rolled Python project-root accessors, neither of which is one of the 60 ${AUTOBOT_PROJECT_ROOT:-…} string literals this issue enumerates — so git grep 'AUTOBOT_PROJECT_ROOT:-' -- '*.py' returning 0 would not have caught either:

    accessor fallback when the marker walk finds nothing
    autobot_shared/ssot_config.py::_find_project_root Path(os.environ.get("AUTOBOT_BASE_DIR", "/opt/autobot"))
    autobot-backend/pki/config.py::_find_project_root Path(config.base_dir) — same default, reached via config

    Both walk upward for a .env, which is git-ignored. So in any checkout without one — a fresh clone, a container build, every CI job — the walk finds nothing and the root resolves to the live install. That is Class B of this issue ("defaults to the live install"), expressed in Python rather than shell, in the module every other module imports its configuration from: ssot_config then feeds that path to ~20 env_file=str(PROJECT_ROOT / ".env") declarations.

    Measured on a CI runner: PROJECT_ROOT resolved to PosixPath('/opt/autobot'), and ssot_config_test.py::TestProjectRoot::test_project_root_exists failed simply because the directory is not there.

    #13551 fixes the ssot_config accessor only — the .env walk is preserved unchanged (deployments place .env above the code directory, so bounding it is a separate call), and a checkout-root fallback is inserted ahead of the live-install default. pki/config.py still carries the duplicate and is untouched, because changing it moves PKI certificate path resolution — that belongs to Task 1's consolidation here, not to a test-failure batch.

    Also relevant: #13357 covers the other half of the same accessor — the walk is unbounded, so from inside a git worktree it escapes into the neighbouring main checkout's .env. #13551 does not change that behaviour.

  5. mrveiss commented on Aug 4, 2026

    @mrveiss
    OwnerAuthor

    The fallback resolves to the live install, confirmed on a real machine

    Owner confirmed AutoBot is installed at /opt/autobot. That makes the ssot_config fallback concrete rather than theoretical:

    # autobot_shared/ssot_config.py::_find_project_root
    for parent in [current] + list(current.parents):
        if (parent / ".env").exists():
            return parent
    # Fallback to runtime location (env var or /opt/autobot)
    return Path(os.environ.get("AUTOBOT_BASE_DIR", "/opt/autobot"))

    Verified present on a developer box: /opt/autobot, owner autobot, 31 entries.

    So on any machine where AutoBot is installed and no .env is reachable, PROJECT_ROOT silently becomes the live install directory. CLAUDE.md states "Codebase is source of truth: never edit /opt/autobot/" — but the resolver can make PROJECT_ROOT be /opt/autobot, and 181 sites read or derive from it. Nothing in the resolution announces which branch it took.

    .env is git-ignored, which is what makes the fallback reachable at all — a fresh clone or CI job never has one.

    Three different outcomes for the same code, which is why this was confusing

    context branch taken result
    main checkout .env walk finds the repo's own .env correct, legitimately
    a git worktree walk escapes upward to the main checkout's .env (#13357) accidentally correct
    CI runner no .env anywhere → fallback /opt/autobot, which does not exist → test_project_root_exists fails

    I want to correct something I relayed earlier: I described the local pass as being caused by the worktree escape. That is true in a worktree; in the main checkout the walk legitimately finds the repo's own .env. Both paths mask the CI failure, but they are different mechanisms and only the worktree one is #13357.

    Seven distinct project-root definitions, not one

    autobot_shared/ssot_config.py          _find_project_root()      .env walk, /opt/autobot fallback
    autobot-backend/pki/config.py          _find_project_root()      duplicate of the above
    autobot-backend/pki/cli.py             parents[2]
    autobot-backend/utils/template_loader  parent.parent.parent
    autobot-backend/utils/hot_reload_mgr   _SRC_ROOT.parent
    autobot-backend/api/codebase_analytics/storage.py   parent x4
    autobot-backend/conversation_file_manager.py:46     Path(__file__).parent
    

    That last one is worth singling out: Path(__file__).parent is autobot-backend/, not a project root at all — and conversation_file_manager.py:127-128 derives data/conversation_files and conversation_files.db from it. So that data lands somewhere different from every other definition's idea of the root.

    This is the concrete shape of what this issue describes. Related: #13572 (19 OpenAPI defaults publish absolute paths derived from PROJECT_ROOT, which blocks correcting the resolver), #13357 (the unbounded .env walk), and #13551 (whose test_project_root_exists box stays open on exactly this).

  6. mrveiss commented on Aug 5, 2026

    @mrveiss
    OwnerAuthor

    Ran a full-repo hunt for hardcoded absolute paths. This issue's two classes are accurate but cover
    roughly a tenth of the /opt/autobot literals in the repo.
    The rest carry no placeholder at all, so
    no search for AUTOBOT_PROJECT_ROOT can find them.

    Full audit: docs/audit/hardcoded_absolute_paths_2026_08_05.md

    Counts

    2278   raw absolute-path literals, whole working tree
    1635   tracked, non-generated files
     416   defect candidates after triage
     163   /opt/autobot literals in non-test source
      14     carry ${AUTOBOT_PROJECT_ROOT:-...}   <- this issue's Class A + B
     149     plain literal, no placeholder        <- uncovered
      17       comments / docstring prose
     132       active code
      82         shipped runtime (excl. scripts/, debug/, *.stories.*, install.sh)
    

    Triage that produced 416 from 1635 — the excluded categories are correct as literals, not defects:
    docs 461, test fixtures 302, ansible/template/unit deploy targets 258, system binaries 159, system
    files (/etc/os-release, /proc) 39.

    Also ruled out: storybook-static/ contains 580 /home/martins/... literals, but it is gitignored
    and git ls-files reports 0 tracked files there. Local build output, not a repo problem — recorded so
    a later scan does not re-raise it.

    The uncovered class

    Bare hardcoded paths, by kind (132 active-code):

    python  57      shell   42      ts      19      vue      7      yaml     4      json     3
    

    By component: autobot-slm-backend 55, autobot-infrastructure 39, autobot-frontend 19,
    autobot-backend 11, autobot_shared 11, autobot-slm-frontend 10.

    Runtime defaults — these decide where real data is written

    Module-level constants and default arguments, in effect on import in the shipped services:

    autobot-backend/security_layer.py:32
        _AUDIT_LOG_FILE_DEFAULT = "/opt/autobot/logs/audit.log"
    
    autobot-backend/api/codebase_analytics/source_paths.py:7
        CODE_SOURCES_BASE: Path = Path("/opt/autobot/data/code-sources")
    
    autobot-backend/services/execution/snapshot_index.py:25
        _DEFAULT_SNAPSHOT_PATH = "/opt/autobot/snapshots"
    
    autobot-backend/services/autoresearch/config.py:26
        default_factory=lambda: Path(config.misc.autoresearch_dir or "/opt/autobot/autoresearch")
    autobot-backend/services/autoresearch/config.py:77
        default_factory=lambda: Path(config.misc.autoresearch_data_dir or "/opt/autobot/autoresearch/data")
    
    autobot-backend/cli/doctor.py:113
        def check_env_file(env_path: str = "/opt/autobot/autobot-backend/.env") -> CheckResult:
    

    Two things stand out. The or "/opt/..." fallbacks in autoresearch/config.py fire whenever the
    config key is unset or blank — the same blank-value-defeats-the-default shape as #12782 and
    #13220. And cli/doctor.py is worse than a bad default: the diagnostic reads the live install's
    .env regardless of which checkout invoked it, so it reports on a system the developer is not
    running.

    The frontend is not mentioned in this issue and has the same gap

    autobot-frontend/src/composables/analytics/useSourceRegistry.ts:108
        const rootPath = ref(savedPath || '/opt/autobot')
    autobot-frontend/src/composables/analytics/useCodeIntelScores.ts:212
        rootPath.value === '/opt/autobot' ||
    autobot-frontend/src/views/EvolutionView.vue:288   placeholder="/opt/autobot"
    autobot-frontend/src/views/EvolutionView.vue:387   repo_path: '/opt/autobot',
    

    useCodeIntelScores.ts:212 branches on the literal, so behaviour depends on a path string the
    deployment may not use.

    A resolver already exists

    autobot_shared/ssot_config.py:67 — PROJECT_ROOT = _find_project_root(). All 132 bypass it. Note
    that ssot_config.py itself holds 7 absolute literals, so the module defining the canonical root is
    among the offenders.

    Highest-density files

    [24] autobot-infrastructure/shared/scripts/bulletproof-frontend/zero-downtime-update.sh
    [14] autobot-slm-backend/api/code_sync.py
    [ 9] autobot-infrastructure/shared/scripts/utilities/sync-all-vms.sh
    [ 8] autobot-infrastructure/shared/scripts/utilities/complete-vm-sync-templates.sh
    [ 8] install.sh
    [ 7] autobot_shared/ssot_config.py
    

    Deliberately not asserted as defects

    Infrastructure templates that name the install they render into
    (browser-start.sh:11, browser-status.sh:46, frontend-status.sh:55, ollama-start.sh:30) may be
    correct by design. The deciding question is whether the install prefix is ever configurable. Listed
    for a decision rather than counted as broken.

    Other prefixes, recorded so they are not lost

    Suggested scope change

    1. Widen this issue to the plain-literal class, or split it out — either way it needs to be findable,
      because grepping AUTOBOT_PROJECT_ROOT misses 149 of 163.
    2. Fix the six runtime defaults first: small, and they govern audit logs and snapshots.
    3. Add a guard test enumerating /opt/autobot literals in tracked non-test source against an
      explicit allowlist — same shape as repo_tests/audio_extension_allowlist_test.py from tech-debt(transcriber): allowed-audio-extension set duplicated across three modules, one of them a security boundary #13512.
      bug(infrastructure): 99 scripts use sys.path.append("${AUTOBOT_PROJECT_ROOT...}") — Python never expands shell variable syntax #4945 was correctly scoped to sys.path and still left siblings nothing detected; without a guard
      this regrows.
  7. mrveiss commented on Aug 5, 2026

    @mrveiss
    OwnerAuthor

    Task 1 in PR #13652. Two corrections to this issue's plan, both from auditing before building.

    1. The proposed accessor would have been a duplicate. ssot_config.PROJECT_ROOT already resolves the root correctly — #13646 landed the checkout step. A new autobot_shared.paths.project_root() beside it would have been a second implementation of the very thing this issue is about. Instead the logic moved into a stdlib-only autobot_shared/paths.py and ssot_config delegates to it.

    2. There were four implementations, not one. Beyond ssot_config:

    Site Defect
    autobot-backend/pki/config.py own _find_project_root(), no checkout step — from a source tree with no .env it pointed PKI's certificate paths at the deployed install
    autobot_shared/env_drift_detector.py hand-copied walk, commented "Mirror ssot_config._find_project_root() logic", drifted the same way
    autobot-infrastructure/shared/scripts/run_code_analysis.py a third form, Path(__file__).parent.parent (a local sys.path bootstrap — left to the migration PR)

    The first two are consolidated in #13652.

    A live bug this exposed

    Implementing the resolver exactly as proposed here — env var, then a .env walk, then a checkout-marker walk — reproduces a defect present on the base branch right now:

    project_root() from .worktrees/issue-13149  ->  <main tree>      # wrong
    

    Worktrees are git-ignored and carry no .env, so the .env pass climbs past the worktree root and matches the main tree's .env. Every worktree resolved its project root to a different tree — and this repository's whole workflow runs from worktrees. Fixed by making it one walk with a checkout root as a hard boundary. CI is unaffected (it checks out directly and has no .env either way); local worktree runs were wrong and now are not.

    Count corrections

    • Class A: 57 occurrences in 41 files, not 60 in 40. Two of the 57 are prose (a comment in mcp_security_test.py, a docstring in webhook_authentication_security_test.py) — not defects, and the Task 1 gate has to tell them apart from real path literals.
    • Class B: 36 shell files, not 34.

    Remaining

    Tasks 2-5 are unstarted. The gate is deliberately held back to ship with the 57 migrations, since it cannot pass until they land. This issue stays open until then.

  8. mrveiss commented on Aug 5, 2026

    @mrveiss
    OwnerAuthor

    Task 2 is blocked on a decision, not on effort

    The 28 Class-A sites under autobot-infrastructure/ cannot take from autobot_shared.paths import project_root as they stand. autobot_shared is not importable from them:

    $ python3 autobot-infrastructure/shared/scripts/<script>.py
    NOT importable; sys.path[0] = <repo>/autobot-infrastructure/shared/scripts
    

    autobot_shared is not pip-installed, and for python3 path/to/script.py Python puts the script's own directory on sys.path, never the working directory. Adding the import to these 28 files would turn a wrong-path bug into an immediate ModuleNotFoundError — strictly worse than what is there now.

    The circularity: the usual fix is a sys.path bootstrap ahead of the import, but the bootstrap has to locate the repo root to do it — which is the exact computation this issue exists to centralise. A bootstrap of parents[3] is one more hardcoded root derivation, just spelled differently.

    Three ways out, and picking between them is an owner call:

    Option Cost Notes
    A. Make the repo importable — pip install -e . in the venvs, or a .pth one-time, touches env setup scripts then just import; no per-file bootstrap. Cleanest, but changes how environments are provisioned
    B. A 3-line marker-search bootstrap in each of the 28 28 files, mechanical duplicates a minimal walk 28 times; the duplication is 3 lines instead of a path literal, but it is still duplication
    C. Leave these 28 and scope the gate to importable trees none now honest, but git grep never reaches 0 and this issue's acceptance is not met

    I have not guessed. Nothing under autobot-infrastructure/ has been touched.

    What did land

    autobot-backend (7 files, 9 occurrences) migrated in branch issue-13149-migration, stacked on #13652:

    occurrences: 57 -> 50
    python3 -m pytest -q autobot_shared/paths_test.py autobot_shared/ssot_config_test.py \
                         autobot-backend/pki/ autobot-backend/monitoring/monitoring_and_alerts_test.py
    60 passed
    

    A second correction: the sites are not one kind

    Treating all 57 uniformly is wrong. They fall into three groups:

    1. Genuine defects — Path("${…}/tests/results").mkdir(), which creates or misses a directory literally named ${AUTOBOT_PROJECT_ROOT:-. This is what the issue describes, and what I fixed.
    2. Inert fixture strings — autobot-slm-backend/api/code_source_test.py passes the literal as a mock return value and then asserts it appears in an error message; tests/services/git_tracker_test.py uses it as a fake repo_path. The value is arbitrary there. Substituting the real project root is churn and mildly weakens the test, whose point is "a path that does not exist". These want an obviously-fake constant instead.
    3. Prose — a comment in autobot-backend/mcp/mcp_security_test.py and a docstring in api/webhook_authentication_security_test.py that describe the pattern. Not defects.

    Group 3 is why the Task 1 gate must be AST-based, not a git grep. A grep gate would force the prose to be reworded to satisfy a checker, and would also fire on this issue's own acceptance criterion.

    One more discovery

    autobot-backend/advanced_rag_optimizer_rerank_test.py defines _WORKTREE pointing at .worktrees/issue-2034/autobot-backend and never references it. Residue of finished work. I migrated it rather than removing it; whether it should simply go is an owner call.

    Also outside the stated classes

    The placeholder additionally appears in autobot-backend/code_analysis/auto-tools/results/*.json (generated analysis output) and in several autobot-slm-backend/ansible/playbooks/*.yml. The JSON files are stale generated artefacts — regenerating them is the fix, not editing them. Both belong with Task 5.

  9. 23 remaining items

  10. mrveiss commented on Aug 7, 2026

    @mrveiss
    OwnerAuthor

    Tasks 1, 3, 4 and 5 have landed

    PR #13652 (a4e3bd465) — Task 1, the canonical accessor.
    PR #13659 (81950bdf2) — Tasks 3, 4 and 5.

    Verified on the merged base:

    scripts/lib/project_root.sh present            yes
    .sh files sourcing the helper                  37
    .sh files with the live-install default         1   (project_root.sh's own doc comment)
    .py files with the placeholder                 33
    

    Class B is complete: every shell script that defaulted to /opt/autobot/code_source now resolves from its own location. The destructive ones went first — rotate_logs.sh was gzipping and -deleteing the live install's logs when run from any checkout, which is the #13092 failure class repeating.

    Task 1 also fixed a live bug found while writing it: the two-pass walk (.env across all ancestors, then markers across all ancestors) escaped every worktree and resolved to the main tree's .env. Since this repository's whole workflow runs from worktrees, each one's config, log and results paths pointed at another tree. A single walk with a checkout root as a hard boundary fixes it, pinned in both directions.

    What remains

    Task 2 — the 28 autobot-infrastructure/ scripts — is still blocked on a decision, not on effort. They cannot take from autobot_shared.paths import project_root:

    $ python3 autobot-infrastructure/shared/scripts/<script>.py
    NOT importable; sys.path[0] = <repo>/autobot-infrastructure/shared/scripts
    

    autobot_shared is not pip-installed, and Python puts the script's directory on sys.path, never the cwd. Adding the import to all 28 converts a wrong-path bug into an immediate ModuleNotFoundError. The obvious sys.path bootstrap is circular — it must locate the repo root before it can import the thing that locates the repo root.

    Three options were costed earlier in this thread (make the repo importable; a 3-line marker-search bootstrap per script; scope the gate to importable trees). None chosen, nothing touched.

    The AST gate ships with Task 2, since it cannot pass while those 28 remain. Two of the 33 remaining Python occurrences are prose — a comment and a docstring — which is why the gate must parse rather than grep.

    Discovered along the way

    Six distinct ad-hoc root derivations, beyond the two classes this issue describes: ssot_config, pki/config.py, env_drift_detector.py, run_code_analysis.py, log_aggregator.py:127 (wrong for its own location), and api/logs.py:55. The first three are consolidated by #13652.

    Task 5's per-consumer audit is at docs/audit/project_root_yaml_json_audit.md. Its one confirmed bug — compliance audit logs written to a literal ${AUTOBOT_PROJECT_ROOT:- directory — is #13658, fixed and closed.

  11. mrveiss commented on Aug 11, 2026

    @mrveiss
    OwnerAuthor

    The numbers in this issue are stale — most of it has already shipped

    Verified against Dev_new_gui tip 5dfe77607 (tracked files only, via git grep). Recording this because anyone picking the issue up from its current text would redo finished work.

    claim reality
    34 shell scripts default to the live install 0 remain — 37/37 now source scripts/lib/project_root.sh. The only string match left is that helper's own doc comment.
    60 unexpanded Python literals 34 files still match the pattern, but only 28 files / ~40 occurrences are real defects, all under autobot-infrastructure/shared/

    The other 6 Python matches are intentional and should not be "fixed": autobot_shared/paths.py (the resolver itself), compliance_manager.py, two test-prose files, a regression fixture and a guard-test fixture.

    The canonical mechanism exists and is sound

    autobot_shared/paths.py::project_root() (stdlib-only) and scripts/lib/project_root.sh, cross-verified consistent. The duplicate resolvers in pki/config.py and env_drift_detector.py were consolidated in PR #13652. So this is no longer "build a canonical source" — it is "finish migrating onto the one that exists".

    What is actually dangerous

    Only 6 files / 7 sites default to /opt/autobot, i.e. can read or write live-install state from a dev run:

    • security_layer.py, source_paths.py, snapshot_index.py, autoresearch/config.py (×2), cli/doctor.py
    • sync-user-backend.yml (Ansible)

    The 28-file infra remnant is untidy, not dangerous — those create local junk paths rather than touching /opt/autobot.

    Decomposition into dispatchable slices

    Non-overlapping in files, ordered by risk:

    slice scope notes
    A the 6 dangerous Python runtime defaults highest risk, verifiable without a host — do first
    B sync-user-backend.yml structural fix is host-free; behavioural proof needs a real deploy
    C1 fix the broken editable install (see below) small, packaging-only, unblocks C2
    C2 the 28 infra scripts blocked on the import-path decision since 2026-08-05
    D 4 frontend literals independent, parallelisable with everything
    E cleanup capstone + AST pre-commit gate must land last — the gate cannot pass CI while C2's files remain

    New finding, filed separately

    The autobot_shared editable install already exists in the venv but is silently broken: import autobot_shared fails from every directory including the repo root, because autobot_shared/pyproject.toml's package discovery registers only the submodules, never the top-level package.

    That matters here because it is exactly the mechanism the blocked "Option A" import-path decision was weighing. Fixing it is cheap and reframes that decision rather than answering it in the abstract.

    Slices A and C1 are being dispatched now.

  12. github-actions commented on Aug 11, 2026

    @github-actions
    Contributor

    PR #14046 (merged to Dev_new_gui) references this issue with a close keyword.

    fix(config): route 6 highest-risk /opt/autobot runtime defaults through project_root() (#13149)

    If this issue is fully resolved, close it manually. If work remains, no action is needed.

  13. github-actions commented on Aug 25, 2026

    @github-actions
    Contributor

    PR #15040 (merged to Dev_new_gui) references this issue with a close keyword.

    fix(config): one canonical project root, and no path that resolves to the wrong thing silently (#13149, #13357, #14113, #14892, #14070)

    If this issue is fully resolved, close it manually. If work remains, no action is needed.

  14. mrveiss commented on Aug 25, 2026

    @mrveiss
    OwnerAuthor

    Closure verification — merged base 5633e418c (PR #15040)

    Verified against origin/Dev_new_gui (the merged tree), not the PR branch.

    Criterion file:line evidence Verdict
    git grep 'AUTOBOT_PROJECT_ROOT:-' -- '*.py' returns 0 Grep returns 29 hits / 17 files. Every one is prose (comment/docstring documenting this defect) or a derived, re-proved exemption: autobot-backend/security/enterprise/compliance_manager.py:88 (_LEGACY_AUDIT_ROOT — must retain the literal to find the junk tree the bug created, or up to 2555 days of encrypted audit records are orphaned, #13658), repo_tests/compliance_audit_path_test.py:101, scripts/check_ansible_file_references_test.py:79. All three are registered with an anchor symbol and a site count in tools/lint/check_no_shell_placeholder_paths.py:125-149, re-checked on every run so the file cannot be rewritten around the exemption and a fresh defect cannot inherit its pass. Zero live path-resolution sites remain. Met on substance — a literal zero would require deleting the bug's own documentation and the migration that depends on the literal
    Shell scripts resolve their root from a shared helper; none embeds the live-install path as a default scripts/lib/project_root.sh is sourced by 38 scripts. Spot-checked every risky script the issue named — the two ansible deploy scripts, cleanup-disk-space.sh, rotate_logs.sh, bulletproof-frontend/zero-downtime-update.sh, scripts/service-auth/emergency-rollback.sh, sync-frontend.sh — all source it. The only surviving :- live-install literal in any *.sh is scripts/lib/project_root.sh:11, inside the helper's own docstring showing the anti-pattern. run_agent.sh:25 defaults to BASH_SOURCE, not the install. Met
    A gate rejects reintroduction tools/lint/check_no_shell_placeholder_paths.py, run in CI at .github/workflows/code-quality.yml:496 (--audit). It now reassembles f-strings via ast.unparse (:184-214), closing the hole that hid a live offender from the previous ast.Constant-only walk. Companion gate tools/lint/check_no_live_install_sys_path_default.py is wired at code-quality.yml:222. Met
    Config/YAML occurrences audited per consumer, not bulk-edited Four files / five occurrences traced to their consumer in PR #15040 (logging.yml:15, promtail-config.yml:40,50, compliance.yaml:15, threat_detection.yaml:28). Result: none is broken; four are inert (no reader) and one is inside a comment block. Supported by git grep expandvars -- '*.py' returning no implementation, so no YAML loader expands ${...} anywhere. Met — the criterion's verb is audit and confirm per consumer, and that is what was delivered

    Root cause closed: autobot_shared/paths.py::project_root() is the single resolver; autobot_shared/ssot_config.py:63 takes it, and autobot_shared/ssot_constants.py:394 now reads PROJECT_ROOT: Path = CANONICAL_PROJECT_ROOT instead of the second open-coded Path(__file__).parent.parent. Two competing Python root resolvers became one.

    Not blocking: the inertness finding is genuinely new — a config that reads as if it configures something and configures nothing — and is tracked with acting-on-it criteria as #15044, together with nine further Ansible-playbook occurrences the criterion did not name. That is the correct handling of a new finding, not an unmet criterion of this issue.

    No sub-issues are linked to this issue (sub_issues API returns empty); the "Suggested decomposition" section was never filed as children, so the Acceptance list above is the binding set.

    Closing.

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