Skip to content

Bundle run for apps sends local filesystem path instead of workspace path when an artifact path and app source_code_path point to the same directory #4924

@sriramrel

Description

@sriramrel

Description

When a bundle defines both an artifacts entry and an apps resource whose paths resolve to the same local directory, bundle run sends a local filesystem path (e.g., /Users/me/project/src) to the Databricks Apps deployment API instead of the expected workspace path (e.g., /Workspace/Users/me@company.com/.bundle/app/target/files/src). The API rejects this with:

Error: Source code path must be a valid workspace path.

The root cause is a shared seen cache in the translateContext struct (bundle/config/mutator/translate_paths.go). Artifact paths are translated using TranslateModeLocalAbsoluteDirectory (which produces local filesystem paths), and the result is cached in t.seen[localPath]. When the app's source_code_path resolves to the same localPath, it hits the cache and returns the local absolute path instead of calling translateDirectoryPath, which would have produced path.Join(t.remoteRoot, localRelPath) — the correct workspace path.

CLI Version

Databricks CLI v0.292.0

Steps to Reproduce

1. Create a minimal bundle structure:

my-app/
├── databricks.yml
├── resources/
│   └── platform.yml
└── src/
    ├── app.py
    ├── pyproject.toml
    └── requirements.txt

2. databricks.yml:

bundle:
  name: my-app

include:
  - resources/*.yml

targets:
  dev:
    mode: development
    default: true

3. resources/platform.yml:

artifacts:
  app_wheel:
    type: whl
    build: uv build
    path: ../src

resources:
  apps:
    my-app:
      name: my-app
      source_code_path: ../src

Note: Both artifacts.app_wheel.path and resources.apps.my-app.source_code_path resolve to the same directory (src/ relative to bundle root).

4. src/app.py:

print("hello")

5. src/pyproject.toml:

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "my-app"
version = "0.1.0"

6. Deploy and run:

databricks bundle deploy -t dev --force-lock
databricks bundle run -t dev my-app

Expected Behavior

bundle run should send a workspace path to the Apps API:

{
  "mode": "SNAPSHOT",
  "source_code_path": "/Workspace/Users/me@company.com/.bundle/my-app/dev/files/src"
}

Actual Behavior

bundle run sends a local filesystem path:

{
  "mode": "SNAPSHOT",
  "source_code_path": "/Users/me/project/my-app/src"
}

The API responds with 400 Bad Request:

Error: Source code path must be a valid workspace path.

Debug Output

POST /api/2.0/apps/my-app/deployments
>   "source_code_path": "/Users/me/project/my-app/src"
< HTTP/2.0 400 Bad Request

Root Cause Analysis

In bundle/config/mutator/translate_paths.go, the translateContext struct has a seen map that caches translation results:

type translateContext struct {
    // ...
    seen map[string]string
    // ...
}

In rewritePath (line ~140):

localPath := filepath.Join(dir, input)
if interp, ok := t.seen[localPath]; ok {
    return interp, nil  // Returns cached result regardless of TranslateMode
}

In translatePaths.Apply (line ~336), translations run in this order:

return applyTranslations(ctx, b, t, []func(...){
    t.applyJobTranslations(...),
    t.applyJobTranslations(...),
    t.applyPipelineTranslations(...),
    t.applyPipelineTranslations(...),
    t.applyArtifactTranslations,    // ← Runs first, uses TranslateModeLocalAbsoluteDirectory
    t.applyAppsTranslations,        // ← Runs second, uses TranslateModeDirectory
})
  1. Artifact translation runs with TranslateModeLocalAbsoluteDirectory for path: ../src

    • Normalizes to src relative to bundle root
    • localPath = /Users/me/project/my-app/src
    • translateLocalAbsoluteDirectoryPath returns /Users/me/project/my-app/src
    • Cached: t.seen["/Users/me/project/my-app/src"] = "/Users/me/project/my-app/src"
  2. App translation runs with TranslateModeDirectory for source_code_path: ../src

    • Normalizes to src relative to bundle root
    • localPath = /Users/me/project/my-app/src (same key)
    • Cache hit: returns /Users/me/project/my-app/src immediately
    • Never calls translateDirectoryPath which would return path.Join(remoteRoot, "src")

Impact

  • Affects any bundle that has an artifact and an app resource pointing to the same directory
  • Common pattern: building a wheel from the app source directory
  • Works without the artifacts section (no cache poisoning)
  • Works if source_code_path uses ${workspace.file_path}/src (absolute workspace paths bypass rewritePath entirely)

Suggested Fix

The seen cache key should include the TranslateMode to prevent cross-mode cache hits:

// Option A: Include mode in cache key
cacheKey := fmt.Sprintf("%s:%d", localPath, opts.Mode)
if interp, ok := t.seen[cacheKey]; ok {
    return interp, nil
}
// ...
t.seen[cacheKey] = interp

Or alternatively, artifact translations should use a separate translateContext instance since artifacts intentionally produce local paths while all other resource types produce workspace paths.

Workaround

Use a bundle variable reference for source_code_path instead of a relative path:

resources:
  apps:
    my-app:
      source_code_path: ${workspace.file_path}/src

This bypasses rewritePath entirely because path.IsAbs() returns true for the resolved workspace path.

Metadata

Metadata

Assignees

Labels

BugSomething isn't workingDABsDABs related issues

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions