Skip to content
27 changes: 25 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ env_prefixes = ["TF_", "AWS_", "ARM_"] # only these host vars enter the contain

Semantics include `command`, `args_prefix`, `path_next`, `path_equals`,
`path_last`, `path_last_if_any`, `env_names`, `env_prefixes`, `env_set`,
`project_markers`, `state_group`, `project_volumes`, `shared_volumes` and
`host_mounts`. Unknown keys **fail validation** instead of being silently
`project_markers`, `state_group`, `project_volumes`, `shared_volumes`,
`host_mounts` and `cwd_mode`. Unknown keys **fail validation** instead of being silently
ignored, and a `schema_version` newer than the binary supports fails closed.
Edit the file, then run `cb install` to reconcile shims.

Expand Down Expand Up @@ -189,6 +189,29 @@ claimed by `host_mounts`, on any provider.
> implicitly to an auto-generated shim. A profile that genuinely needs a host
> mount must declare it explicitly.

### Isolated launcher mode (`cwd_mode`)

`cwd_mode` defaults to `"project"`, which preserves the normal behavior of
walking up from the current working directory to find project markers and
bind-mounting the project into the container at `/workspace`. Set it to
`"isolated"` for tools that may be launched from an arbitrary working directory
that is not itself meaningful — for example, a background service or a GUI/MCP
launcher that inherits `C:\Windows\System32` as its CWD and invokes a shim from
there. In isolated mode, ContainerBin skips project-root detection entirely,
sets `--workdir /root`, and does not bind-mount the host CWD at all. Any
argument that looks like a host path is still mapped, but because no path can be
"inside the project" it always uses the existing external-mount path and lands
under `/cb/mounts/N`. `cwd_mode = "isolated"` cannot be combined with:

- `project_volumes` (a project-scoped volume conceptually requires a project identity);
- `project_markers` (dead configuration once project-root detection is skipped);
- `provider = "python"` (the python provider has its own project/compat venv split that
isolated mode would otherwise silently collapse onto the shared global compat environment).

`shared_volumes`, `host_mounts` and environment allowlisting all work exactly as they do in
project mode. Exposed profiles created by `cb expose` from an npm-shaped source profile do
**not** inherit that source's `cwd_mode`.

## Windows path mapping

ContainerBin translates Windows paths in arguments to container paths and
Expand Down
68 changes: 55 additions & 13 deletions internal/cli/cli.go
Comment thread
AviBackToBlack marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,24 @@ func Trace(reg registry.Registry, args []string) error {
if err != nil {
return err
}
root, found := pathmap.FindProjectRoot(cwd, pathmap.ProjectMarkersFor(t))
if !found {
root = cwd
}

raw := append([]string(nil), args[1:]...)
normalized := pathmap.NormalizeToolArgs(t, raw)
workspaceRoot := pathmap.WorkspaceRootFor(t, root)

var root, workspaceRoot string
var found bool
if t.CwdMode == "isolated" {
root = dockerrun.IsolatedRoot
workspaceRoot = "/root"
found = false
} else {
root, found = pathmap.FindProjectRoot(cwd, pathmap.ProjectMarkersFor(t))
if !found {
root = cwd
}
workspaceRoot = pathmap.WorkspaceRootFor(t, root)
}

mapped, mounts, err := pathmap.MapToolArgs(t, root, cwd, workspaceRoot, raw)
if err != nil {
return err
Expand All @@ -75,13 +86,23 @@ func Trace(reg registry.Registry, args []string) error {
fmt.Printf("image: %s\n", t.Image)
fmt.Printf("provider: %s\n", t.Provider)
fmt.Printf("cwd: %s\n", cwd)
fmt.Printf("root: %s\n", root)
fmt.Printf("workspace: %s\n", workspaceRoot)
if t.CwdMode == "isolated" {
fmt.Printf("cwd_mode: isolated\n")
fmt.Printf("workdir: /root\n")
fmt.Printf("project_bind_mount: (none)\n")
} else {
fmt.Printf("root: %s\n", root)
fmt.Printf("workspace: %s\n", workspaceRoot)
}
fmt.Printf("raw: %#v\n", raw)
fmt.Printf("normalized: %#v\n", normalized)
fmt.Printf("mapped: %#v\n", mapped)
if len(mounts) == 0 {
fmt.Printf("mounts: (none beyond %s)\n", workspaceRoot)
if t.CwdMode == "isolated" {
fmt.Printf("mounts: (none beyond explicit host/cb mounts)\n")
} else {
fmt.Printf("mounts: (none beyond %s)\n", workspaceRoot)
}
} else {
fmt.Printf("mounts: %#v\n", mounts)
}
Expand Down Expand Up @@ -297,10 +318,21 @@ func Inspect(reg registry.Registry, args []string) error {
if err != nil {
return err
}
root, found := pathmap.FindProjectRoot(cwd, pathmap.ProjectMarkersFor(t))
if !found {
root = cwd

var root, workspaceRoot string
var found bool
if t.CwdMode == "isolated" {
root = ""
found = false
workspaceRoot = "/root"
} else {
Comment thread
AviBackToBlack marked this conversation as resolved.
root, found = pathmap.FindProjectRoot(cwd, pathmap.ProjectMarkersFor(t))
if !found {
root = cwd
}
workspaceRoot = pathmap.WorkspaceRootFor(t, root)
}

fmt.Printf("name: %s\nimage: %s\nprovider: %s\n", t.Name, t.Image, t.Provider)
lock, lockPath, lerr := lockfile.LoadForRegistry()
if lerr != nil {
Expand All @@ -318,7 +350,17 @@ func Inspect(reg registry.Registry, args []string) error {
if len(t.Command) > 0 {
fmt.Printf("command: %#v\n", t.Command)
}
fmt.Printf("cwd: %s\nroot: %s\nworkspace: %s\n", cwd, root, pathmap.WorkspaceRootFor(t, root))
if t.CwdMode == "isolated" {
fmt.Printf("cwd_mode: isolated\n")
}
fmt.Printf("cwd: %s\n", cwd)
if t.CwdMode == "isolated" {
fmt.Printf("workdir: /root\n")
fmt.Printf("project_bind_mount: (none)\n")
} else {
fmt.Printf("root: %s\n", root)
fmt.Printf("workspace: %s\n", workspaceRoot)
}
if t.StateGroup != "" {
fmt.Printf("state_group: %s\n", t.StateGroup)
}
Expand All @@ -327,7 +369,7 @@ func Inspect(reg registry.Registry, args []string) error {
if e != nil {
return e
}
fmt.Printf("project_volume: %s -> %s\n", pathmap.StatefulProjectVolumeID(t.StateGroup, logical, root, found), pathmap.StatefulWorkspaceDestination(dst, pathmap.WorkspaceRootFor(t, root)))
fmt.Printf("project_volume: %s -> %s\n", pathmap.StatefulProjectVolumeID(t.StateGroup, logical, root, found), pathmap.StatefulWorkspaceDestination(dst, workspaceRoot))
}
for _, spec := range t.SharedVolumes {
logical, dst, e := registry.ParseVolumeBinding(spec)
Expand Down
139 changes: 139 additions & 0 deletions internal/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,145 @@ host_mounts = ["%USERPROFILE%/does-not-exist:/root/missing:ro"]
}
}

func TestInspectDefaultOmitsCwdMode(t *testing.T) {
dir := t.TempDir()
old, _ := os.Getwd()
if err := os.Chdir(dir); err != nil {
t.Fatal(err)
}
defer os.Chdir(old)

reg, err := registry.ParseTOML(`[tools.demo]
image = "demo:1"
provider = "stateless"
`)
if err != nil {
t.Fatalf("parse registry: %v", err)
}

out, err := captureStdout(func() error { return Inspect(reg, []string{"demo"}) })
if err != nil {
t.Fatalf("capture: %v", err)
}
if strings.Contains(out, "cwd_mode") {
t.Fatalf("default inspect output should not contain cwd_mode:\n%s", out)
}
}

func TestInspectPrintsCwdModeIsolated(t *testing.T) {
dir := t.TempDir()
old, _ := os.Getwd()
if err := os.Chdir(dir); err != nil {
t.Fatal(err)
}
defer os.Chdir(old)

reg, err := registry.ParseTOML(`[tools.demo]
image = "demo:1"
provider = "stateful"
state_group = "g"
cwd_mode = "isolated"
shared_volumes = ["cache:/root/.cache"]
`)
if err != nil {
t.Fatalf("parse registry: %v", err)
}

out, err := captureStdout(func() error { return Inspect(reg, []string{"demo"}) })
if err != nil {
t.Fatalf("capture: %v", err)
}
if !strings.Contains(out, "cwd_mode: isolated") {
t.Fatalf("inspect output missing cwd_mode: isolated:\n%s", out)
}
if !strings.Contains(out, "workdir: /root") {
t.Fatalf("inspect output missing /root workdir:\n%s", out)
}
if !strings.Contains(out, "project_bind_mount: (none)") {
t.Fatalf("inspect output missing no-project-mount marker:\n%s", out)
}
if strings.Contains(out, "\nroot:") {
t.Fatalf("isolated inspect output should not print a project root:\n%s", out)
}
if strings.Contains(out, "\nworkspace:") {
t.Fatalf("isolated inspect output should not print a workspace:\n%s", out)
}
}

func TestTraceDefaultStillPrintsRootAndWorkspace(t *testing.T) {
dir := t.TempDir()
old, _ := os.Getwd()
if err := os.Chdir(dir); err != nil {
t.Fatal(err)
}
defer os.Chdir(old)
if err := os.WriteFile(filepath.Join(dir, ".git"), []byte{}, 0644); err != nil {
t.Fatal(err)
}

reg, err := registry.ParseTOML(`[tools.demo]
image = "demo:1"
provider = "stateless"
`)
if err != nil {
t.Fatalf("parse registry: %v", err)
}

out, err := captureStdout(func() error { return Trace(reg, []string{"demo"}) })
if err != nil {
t.Fatalf("capture: %v", err)
}
if !strings.Contains(out, "\nroot:") {
t.Fatalf("default trace output missing root line:\n%s", out)
}
if !strings.Contains(out, "\nworkspace:") {
t.Fatalf("default trace output missing workspace line:\n%s", out)
}
if strings.Contains(out, "cwd_mode") {
t.Fatalf("default trace output should not contain cwd_mode:\n%s", out)
}
}

func TestTraceIsolatedNoProjectMount(t *testing.T) {
dir := t.TempDir()
old, _ := os.Getwd()
if err := os.Chdir(dir); err != nil {
t.Fatal(err)
}
defer os.Chdir(old)

reg, err := registry.ParseTOML(`[tools.demo]
image = "demo:1"
provider = "stateful"
state_group = "g"
cwd_mode = "isolated"
shared_volumes = ["cache:/root/.cache"]
`)
if err != nil {
t.Fatalf("parse registry: %v", err)
}

out, err := captureStdout(func() error { return Trace(reg, []string{"demo"}) })
if err != nil {
t.Fatalf("capture: %v", err)
}
if !strings.Contains(out, "cwd_mode: isolated") {
t.Fatalf("trace output missing cwd_mode: isolated:\n%s", out)
}
if !strings.Contains(out, "workdir: /root") {
t.Fatalf("trace output missing /root workdir:\n%s", out)
}
if !strings.Contains(out, "project_bind_mount: (none)") {
t.Fatalf("trace output missing no-project-mount marker:\n%s", out)
}
if strings.Contains(out, "\nroot:") {
t.Fatalf("isolated trace output should not print a project root:\n%s", out)
}
if strings.Contains(out, "\nworkspace:") {
t.Fatalf("isolated trace output should not print a workspace:\n%s", out)
}
}

func TestTraceHostMountUNCWouldFail(t *testing.T) {
if runtime.GOOS != "windows" {
t.Skip("UNC resolution is only meaningful on Windows")
Expand Down
Loading