Skip to content

PYTHONPATH ordering: deps/ shadows src/ for path-installed dev bundles #269

@mgoldsborough

Description

@mgoldsborough

Symptom

When a developer edits a path-installed Python bundle's source (via the workspace.json { "path": "/path/to/bundle" } install mode), the platform's bundle subprocess silently keeps loading the vendored snapshot from <bundle>/deps/<pkg>/ instead of the live source at <bundle>/src/<pkg>/. The dev's edits don't take effect; restart doesn't help; only manually deleting deps/<pkg>/ (or running make clean-bundle) does.

This caused several hours of confusion during local validation of Phase 2c (synapse-research adoption of the host-resources SDK) — every edit + bundle restart loop appeared to be a no-op until we noticed deps/mcp_research/ shadowing src/mcp_research/.

Root cause

src/bundles/startup.ts:762-769:

const pathParts: string[] = [];
const depsDir = join(resolvedDir, "deps");
if (existsSync(depsDir)) pathParts.push(depsDir);
const srcDir = join(resolvedDir, "src");
if (existsSync(srcDir)) pathParts.push(srcDir);
if (pathParts.length > 0) {
  const existing = spawnEnv.PYTHONPATH;
  spawnEnv.PYTHONPATH = existing ? `${pathParts.join(":")}:${existing}` : pathParts.join(":");
}

deps/ is pushed first, so it comes first on PYTHONPATH. Python's import resolver walks PYTHONPATH in order — deps/mcp_research/ wins over src/mcp_research/. For packed .mcpb bundles this is correct (the .mcpb may only have deps/, no src/). For path-installed bundles where the developer is iterating on src/, it silently shadows their work.

How deps/<pkg>/ gets there

Bundle authors typically run make bundle (or equivalent uv pip install --target ./deps .) to produce the packed .mcpb. That command installs the package itself into deps/, baking a snapshot of src/ from build time. The snapshot survives across edit/test cycles; only make clean-bundle removes it.

Suggested fixes

Pick one (or both):

  1. Reverse the order when pyproject.toml exists alongside src/: developer dev-loop wins, packed bundles unaffected (they don't have a pyproject.toml at the bundle root). Concretely: if existsSync(join(resolvedDir, "pyproject.toml")) && existsSync(srcDir), push srcDir before depsDir.
  2. Or detect & warn: log a startup warning when deps/<pkg>/ and src/<pkg>/ both exist with the same name, naming which one Python will load. Makes the shadowing visible even if it's the right behaviour.

Either alone is helpful; #1 fixes the dev loop, #2 documents reality.

Context

Surfaced during Phase 2c validation. Same root cause likely affects every NimbleBrain dev iterating locally on a Python bundle that's also been packed at least once.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions