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
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:
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
})
-
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"
-
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.
Description
When a bundle defines both an
artifactsentry and anappsresource whose paths resolve to the same local directory,bundle runsends 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:The root cause is a shared
seencache in thetranslateContextstruct (bundle/config/mutator/translate_paths.go). Artifact paths are translated usingTranslateModeLocalAbsoluteDirectory(which produces local filesystem paths), and the result is cached int.seen[localPath]. When the app'ssource_code_pathresolves to the samelocalPath, it hits the cache and returns the local absolute path instead of callingtranslateDirectoryPath, which would have producedpath.Join(t.remoteRoot, localRelPath)— the correct workspace path.CLI Version
Steps to Reproduce
1. Create a minimal bundle structure:
2.
databricks.yml:3.
resources/platform.yml:Note: Both
artifacts.app_wheel.pathandresources.apps.my-app.source_code_pathresolve to the same directory (src/relative to bundle root).4.
src/app.py:5.
src/pyproject.toml:6. Deploy and run:
Expected Behavior
bundle runshould 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 runsends a local filesystem path:{ "mode": "SNAPSHOT", "source_code_path": "/Users/me/project/my-app/src" }The API responds with
400 Bad Request:Debug Output
Root Cause Analysis
In
bundle/config/mutator/translate_paths.go, thetranslateContextstruct has aseenmap that caches translation results:In
rewritePath(line ~140):In
translatePaths.Apply(line ~336), translations run in this order:Artifact translation runs with
TranslateModeLocalAbsoluteDirectoryforpath: ../srcsrcrelative to bundle rootlocalPath=/Users/me/project/my-app/srctranslateLocalAbsoluteDirectoryPathreturns/Users/me/project/my-app/srct.seen["/Users/me/project/my-app/src"] = "/Users/me/project/my-app/src"App translation runs with
TranslateModeDirectoryforsource_code_path: ../srcsrcrelative to bundle rootlocalPath=/Users/me/project/my-app/src(same key)/Users/me/project/my-app/srcimmediatelytranslateDirectoryPathwhich would returnpath.Join(remoteRoot, "src")Impact
artifactssection (no cache poisoning)source_code_pathuses${workspace.file_path}/src(absolute workspace paths bypassrewritePathentirely)Suggested Fix
The
seencache key should include theTranslateModeto prevent cross-mode cache hits:Or alternatively, artifact translations should use a separate
translateContextinstance since artifacts intentionally produce local paths while all other resource types produce workspace paths.Workaround
Use a bundle variable reference for
source_code_pathinstead of a relative path:This bypasses
rewritePathentirely becausepath.IsAbs()returns true for the resolved workspace path.