feat(v0.5): export CTASK_LAUNCH_DIR into child sessions

config.EnvVars gains a 7th launchDir argument. cmd/new, cmd/resume, and
cmd/open pass ws.Meta.LaunchDir. Child sessions can read CTASK_LAUNCH_DIR
to know which subdirectory ctask launched them into.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-22 19:48:23 -04:00
parent 7cfafdc285
commit 509a6d64ea
5 changed files with 39 additions and 19 deletions
+1 -1
View File
@@ -119,7 +119,7 @@ func runNew(cmd *cobra.Command, args []string) error {
return nil
}
envVars := config.EnvVars(ws.Meta.Slug, ws.Meta.Mode, root, ws.Path, ws.Meta.Category, workspace.EffectiveType(ws.Meta))
envVars := config.EnvVars(ws.Meta.Slug, ws.Meta.Mode, root, ws.Path, ws.Meta.Category, workspace.EffectiveType(ws.Meta), ws.Meta.LaunchDir)
return session.Run(session.LaunchOpts{
WsDir: ws.Path,
+1 -1
View File
@@ -42,7 +42,7 @@ func runOpen(cmd *cobra.Command, args []string) error {
return fmt.Errorf("updating metadata: %w", err)
}
envVars := config.EnvVars(ws.Meta.Slug, ws.Meta.Mode, ws.Root, ws.Path, ws.Meta.Category, workspace.EffectiveType(ws.Meta))
envVars := config.EnvVars(ws.Meta.Slug, ws.Meta.Mode, ws.Root, ws.Path, ws.Meta.Category, workspace.EffectiveType(ws.Meta), ws.Meta.LaunchDir)
return session.Run(session.LaunchOpts{
WsDir: ws.Path,
+1 -1
View File
@@ -62,7 +62,7 @@ func doResume(query string, container, useShell, force bool, agentOverride strin
agent = ws.Meta.Agent
}
envVars := config.EnvVars(ws.Meta.Slug, ws.Meta.Mode, ws.Root, ws.Path, ws.Meta.Category, workspace.EffectiveType(ws.Meta))
envVars := config.EnvVars(ws.Meta.Slug, ws.Meta.Mode, ws.Root, ws.Path, ws.Meta.Category, workspace.EffectiveType(ws.Meta), ws.Meta.LaunchDir)
return session.Run(session.LaunchOpts{
WsDir: ws.Path,
+10 -7
View File
@@ -90,17 +90,20 @@ func samePath(a, b string) bool {
// EnvVars returns the environment variables to export into child sessions.
// taskType must be "task" or "project"; an empty value defaults to "task".
func EnvVars(slug, mode, root, workspace, category, taskType string) map[string]string {
// launchDir is the workspace-relative project subdirectory for projects,
// or empty for tasks and pre-v0.5 projects.
func EnvVars(slug, mode, root, workspace, category, taskType, launchDir string) map[string]string {
if taskType == "" {
taskType = "task"
}
return map[string]string{
"CTASK_TASK": slug,
"CTASK_MODE": mode,
"CTASK_ROOT": root,
"CTASK_WORKSPACE": workspace,
"CTASK_CATEGORY": category,
"CTASK_TYPE": taskType,
"CTASK_TASK": slug,
"CTASK_MODE": mode,
"CTASK_ROOT": root,
"CTASK_WORKSPACE": workspace,
"CTASK_CATEGORY": category,
"CTASK_TYPE": taskType,
"CTASK_LAUNCH_DIR": launchDir,
}
}
+26 -9
View File
@@ -145,14 +145,15 @@ func TestResolveProjectRootOverride(t *testing.T) {
}
func TestEnvVars(t *testing.T) {
vars := EnvVars("my-slug", "local", "/abs/root", "/abs/root/cat/ws", "general", "task")
vars := EnvVars("my-slug", "local", "/abs/root", "/abs/root/cat/ws", "general", "task", "")
expected := map[string]string{
"CTASK_TASK": "my-slug",
"CTASK_MODE": "local",
"CTASK_ROOT": "/abs/root",
"CTASK_WORKSPACE": "/abs/root/cat/ws",
"CTASK_CATEGORY": "general",
"CTASK_TYPE": "task",
"CTASK_TASK": "my-slug",
"CTASK_MODE": "local",
"CTASK_ROOT": "/abs/root",
"CTASK_WORKSPACE": "/abs/root/cat/ws",
"CTASK_CATEGORY": "general",
"CTASK_TYPE": "task",
"CTASK_LAUNCH_DIR": "",
}
for k, v := range expected {
if vars[k] != v {
@@ -162,15 +163,31 @@ func TestEnvVars(t *testing.T) {
}
func TestEnvVarsProjectType(t *testing.T) {
vars := EnvVars("p", "local", "/r", "/r/p", "projects", "project")
vars := EnvVars("p", "local", "/r", "/r/p", "projects", "project", "p")
if vars["CTASK_TYPE"] != "project" {
t.Errorf("CTASK_TYPE: got %q, want \"project\"", vars["CTASK_TYPE"])
}
}
func TestEnvVarsEmptyTypeDefaultsToTask(t *testing.T) {
vars := EnvVars("p", "local", "/r", "/r/p", "general", "")
vars := EnvVars("p", "local", "/r", "/r/p", "general", "", "")
if vars["CTASK_TYPE"] != "task" {
t.Errorf("CTASK_TYPE empty fallback: got %q, want \"task\"", vars["CTASK_TYPE"])
}
}
func TestEnvVarsIncludesLaunchDir(t *testing.T) {
env := EnvVars("demo", "local", "/ws", "/ws/2026-04-22_demo", "projects", "project", "demo")
if got := env["CTASK_LAUNCH_DIR"]; got != "demo" {
t.Errorf("CTASK_LAUNCH_DIR: got %q, want %q", got, "demo")
}
}
func TestEnvVarsLaunchDirEmpty(t *testing.T) {
env := EnvVars("demo", "local", "/ws", "/ws/2026-04-22_demo", "general", "task", "")
if got, ok := env["CTASK_LAUNCH_DIR"]; !ok {
t.Errorf("CTASK_LAUNCH_DIR should be set (empty value), got absent")
} else if got != "" {
t.Errorf("CTASK_LAUNCH_DIR: got %q, want empty", got)
}
}