Skip to content

feat(cli): task-setup.sh + per-worktree context registration#891

Merged
buremba merged 5 commits into
mainfrom
feat/task-setup-script
May 18, 2026
Merged

feat(cli): task-setup.sh + per-worktree context registration#891
buremba merged 5 commits into
mainfrom
feat/task-setup-script

Conversation

@buremba
Copy link
Copy Markdown
Member

@buremba buremba commented May 18, 2026

Summary

Adds a team-friendly, Makefile-driven workflow for paired-branch worktrees that don't lose changes inside the packages/owletto submodule, plus the Lobu CLI context plumbing the Mac menubar will need to surface each worktree as a managed server.

  • make task-setup NAME=<name> — creates the worktree at .claude/worktrees/<name> (lobu + submodule on real feat/<name> branches), copies .env, picks free ports, writes .env.local, drops a .task marker, and registers the worktree as a Lobu CLI context with lifecycle: "managed".
  • make task-clean NAME=<name> [FORCE=1] — removes the worktree, both branches, and the Lobu context. Refuses by default when there's uncommitted work or unpushed commits; FORCE=1 overrides.
  • lobu context add now accepts --port, --host, --database-url, --data-dir, --cwd, --lifecycle.
  • lobu context rm <name> (new) — idempotent, refuses the default context.
  • LobuServerConfig.cwd (new field) — "directory the lifecycle owner cd's into before spawning lobu run". No reader yet; menubar update is a follow-up in owletto.

Optional shell-function sugar (task-start / task-resume) documented in scripts/task-setup.sh header — only needed if you want one command that also cd's and exec claude's for you (Make can't move the parent shell).

What this fixes

The four submodule footguns that have been costing us work:

  1. Detached-HEAD trap (AGENTS.md:21–22) — git submodule update checks out a SHA, not a branch; commits there get orphaned. Script forces a real feat/<name> branch in the submodule before any edits.
  2. bun.lock prune (PR fix: restore packages/web entries in bun.lock #654) — bun install before git submodule update --init drops the locked subdir. Script orders correctly.
  3. Missing .env.env is gitignored, so git worktree add leaves a worktree that can't boot. Script copies from the main repo.
  4. Port collisions — parallel make dev fights for :8787 / :8118. Script scans existing .env.local files and picks the next free pair.

Reproducer (red → green)

Red — today, without the script (documented in AGENTS.md:21–22):
git submodule update --init packages/owletto lands the submodule on detached HEAD. Edits + commits there are reachable only via reflog. Next git submodule update or parent git switch orphans them.

Green — with this PR:

$ make task-setup NAME=wrapper-smoke
…
✓ Worktree ready: …/.claude/worktrees/wrapper-smoke
  lobu branch:       feat/wrapper-smoke
  owletto branch:    feat/wrapper-smoke (real named branch, not detached HEAD)
  PORT:              8788
  WORKER_PROXY_PORT: 8119

$ git -C .claude/worktrees/wrapper-smoke/packages/owletto rev-parse --abbrev-ref HEAD
feat/wrapper-smoke           # ← was 'HEAD' (detached) before this PR

# Loss-mode test: commit in submodule lands on the branch (not orphaned)
$ echo 'x' >> …/packages/owletto/README.md \
   && git -C …/packages/owletto add README.md && git commit -m smoke …
$ git -C …/packages/owletto log --oneline feat/wrapper-smoke -1
78b0aa3 smoke: prove commit lands on real branch

Cleanup path verified:

$ make task-clean NAME=cleanup-smoke
error: uncommitted changes in …/.claude/worktrees/cleanup-smoke (pass --force to discard)
make: *** [task-clean] Error 1

$ make task-clean NAME=cleanup-smoke FORCE=1
→ removing worktree …/.claude/worktrees/cleanup-smoke
→ deleting lobu branch feat/cleanup-smoke
Deleted branch feat/cleanup-smoke (was 5ab6d140).
✓ cleaned up task 'cleanup-smoke'

lobu context rm verified end-to-end:

$ lobu context add verify-flow --api-url http://localhost:8788 --port 8788 --cwd /…/verify-flow --lifecycle managed
  Saved context verify-flow -> http://localhost:8788

$ lobu context list
   lobu  https://app.lobu.ai/api/v1
 * verify-flow  http://localhost:8788

$ lobu context rm verify-flow
  Removed context verify-flow

$ lobu context rm verify-flow    # idempotent
  Removed context verify-flow

$ lobu context rm lobu           # refuses default
  Error: Cannot remove the default context "lobu".

Also verified:

  • bun run typecheck clean.
  • bun test packages/cli/src/internal/__tests__/context.test.ts — 11/11 pass (5 new: addContext server config, addContext bare, removeContext + idempotent + refuses-default).

Follow-ups (separate PRs)

  • Owletto-side menubar: read LobuServerConfig.cwd and cd there before spawning lobu run. Until then, context registration is benign — lobu chat -c <name> already routes to the right port via apiUrl.
  • Deprecate ~/Code/owletto: once the wrapper is in active use, remove the standalone clone and the matching AGENTS.md guidance.

Test plan

  • bun run typecheck
  • bun test packages/cli/src/internal/__tests__/context.test.ts (11/11)
  • E2E setup (make task-setup) — submodule branch non-detached, .env present, ports assigned, marker file written
  • Commit-survives-on-branch test in the submodule (the actual bug fix)
  • E2E make task-clean — bails on uncommitted work, completes with FORCE=1, removes worktree + branch
  • lobu context add / rm end-to-end with new flags
  • Live-boot test (make dev on the assigned port without colliding with :8787) — manual, requires running services

Summary by CodeRabbit

  • New Features

    • context add accepts and persists optional server settings (port, host, database-url, data-dir, cwd, lifecycle).
    • context rm <name> added to remove saved contexts idempotently.
    • Task tooling: create/use/clean per-task worktrees with auto port assignment, env handling, symlink switching, and optional CLI context registration.
  • Chores

    • Added Makefile targets: task-setup, task-clean, task-use; added .task to .gitignore.
  • Tests

    • Added tests for server config persistence, backward compatibility, and context removal behavior.

Review Change Stack

Adds scripts/task-setup.sh and extends `lobu context add` so paired
worktrees register themselves as managed contexts the menubar can spawn.

Fixes the "we keep losing changes" submodule pain:
1. Detached-HEAD trap — script switches packages/owletto onto a real
   feat/<name> branch before any commits can happen.
2. bun.lock prune — submodule init runs before `bun install`.
3. Missing .env in fresh worktrees — copies from the main repo.
4. Port collisions — picks next free PORT / WORKER_PROXY_PORT.

CLI changes:
- LobuServerConfig.cwd: directory the lifecycle owner cd's into before
  spawning `lobu run`. No reader yet; menubar follow-up in owletto.
- `lobu context add` now accepts --port, --host, --database-url,
  --data-dir, --cwd, --lifecycle.
- addContext() takes an optional server config.

Companion shell functions documented in the script header; not
auto-installed (user adds to ~/.zshrc).
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 18, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d574c82e-701e-483e-9daa-b31e270b3f46

📥 Commits

Reviewing files that changed from the base of the PR and between d94ea1d and 0f43709.

📒 Files selected for processing (1)
  • scripts/task-use.sh
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/task-use.sh

📝 Walkthrough

Walkthrough

Extended CLI context commands to accept and persist optional per-context server configuration (including cwd) and added Makefile targets plus scripts to create/clean per-task git worktrees with port allocation, .task markers, and optional Lobu CLI context registration/removal.

Changes

Server Configuration in CLI Context

Layer / File(s) Summary
Server config type and context persistence
packages/cli/src/internal/context.ts
LobuServerConfig adds optional cwd. addContext(name, apiUrl, server?) normalizes and persists server alongside apiUrl. normalizeServerConfig trims and retains cwd when present.
CLI command wiring and option parsing
packages/cli/src/commands/context.ts, packages/cli/src/index.ts, packages/cli/src/internal/index.ts
contextAddCommand accepts server configuration options, constructs a typed server object when provided, and forwards it to addContext. context add gains --port, --host, --database-url, --data-dir, --cwd, and --lifecycle (with validation). A new context rm subcommand calls contextRmCommand and removeContext is re-exported.
Server config persistence and removal tests
packages/cli/src/internal/__tests__/context.test.ts
Tests added/updated to verify addContext persists server fields when provided, remains backward-compatible when omitted, rejects overwriting the default context, and that removeContext deletes contexts, resets active context, is idempotent, and rejects removing the default.

Per-Task Worktree and Environment Setup Script

Layer / File(s) Summary
Makefile targets for task setup/clean
Makefile
Adds task-setup, task-clean, and task-use to .PHONY, documents them in make help, and invokes the corresponding scripts requiring NAME (and optional FORCE).
Task cleanup script
scripts/task-clean.sh
Validates task name, performs safety checks for uncommitted/unpushed changes unless --force, removes worktree and feature branches, and optionally removes the Lobu CLI context.
Script header and argument validation
scripts/task-setup.sh
Adds usage docs, enables strict bash behavior, validates kebab-case <name>, rejects reserved CLI context names, computes worktree/branch paths, and supports refresh-only mode when the worktree exists.
Worktree and submodule initialization
scripts/task-setup.sh
On first creation: fetches origin, creates a worktree on feat/<name> (reusing or creating branch), initializes and switches packages/owletto submodule to a named branch, and runs bun install.
Environment and port configuration
scripts/task-setup.sh
Copies main .env when present, scans other worktrees' .env.local to determine used PORT and WORKER_PROXY_PORT, reuses valid ports or assigns incremented ports to avoid collisions, and writes .env.local with LOBU_TASK_NAME.
Lobu context registration and final output
scripts/task-setup.sh, .gitignore
Writes a .task marker file (ignored by .gitignore), conditionally registers a managed Lobu CLI context when lobu is on PATH (non-fatal), and prints a readiness summary with branches, ports, and manual push/launch instructions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • lobu-ai/lobu#889: Extends packages/cli/src/internal/context.ts to support per-context lifecycle in LobuServerConfig, reading and normalizing the config that this PR writes via addContext.
  • lobu-ai/lobu#839: Extends packages/cli/src/internal/context.ts with LobuServerConfig and adds getServerConfig/setServerConfig plumbing that loads and saves the server block persisted by this PR's addContext.

Suggested labels

skip-size-check

🐰 I dug a branch where worktrees sleep,
Ports lined up so none would creep,
Each context keeps its server and cwd,
A tiny .task marks paths to tread,
Hoppity—ready, push and run, good leap!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main changes: adding task-setup.sh scripting and per-worktree Lobu CLI context registration functionality.
Description check ✅ Passed The description is comprehensive and complete. It covers the summary, test plan, and notes sections with detailed explanations of the changes, bug fixes, reproducers, and follow-ups.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/task-setup-script

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 18, 2026

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 97.05882% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
packages/cli/src/internal/context.ts 97.05% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/cli/src/index.ts`:
- Around line 546-550: The CLI option parser for "--port <port>" currently uses
Number.parseInt(value, 10) which accepts partial/invalid inputs; change the
parser function for the "--port <port>" option to validate that the entire input
is a decimal integer (e.g., /^\d+$/) and that the numeric value falls in the
valid TCP port range (1–65535); on invalid input throw a descriptive Error (so
the CLI fails at parse time) and on valid input return the parsed Number; update
the option's anonymous parser where "--port <port>" is defined to replace
Number.parseInt with this strict-validate-and-parse logic.

In `@scripts/task-setup.sh`:
- Around line 105-111: The loop that scans for PORT/WORKER_PROXY_PORT only
iterates worktree .env.local files (for env_local in
"$repo"/.claude/worktrees/*/.env.local) and therefore misses the repository root
.env.local; update the scan used by the function that computes highest_port and
highest_proxy so it also reads "$repo/.env.local" (either prepend a check for
"$repo/.env.local" before the for-loop or extend the glob to include it), parse
PORT and WORKER_PROXY_PORT the same way using the existing awk/tr -d logic, and
ensure you still skip the current worktree_dir/.env.local and update
highest_port and highest_proxy accordingly.
- Around line 71-75: The worktree creation currently only checks local
refs/heads/$branch before creating a new branch from origin/main, which discards
remote-only branch history; change the logic around the git show-ref and git
worktree add calls to also detect a remote branch (refs/remotes/origin/$branch)
and, if present, add the worktree from that remote ref (use git worktree add
"$worktree_dir" "origin/$branch") instead of creating a new branch from
origin/main; keep using the existing variables repo, branch and worktree_dir and
follow the same pattern used in the submodule logic (check remote ref first,
then fallback to creating from origin/main).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9a586b45-37d4-486e-95f3-1b6c9f232d02

📥 Commits

Reviewing files that changed from the base of the PR and between 5ab6d14 and 2372770.

📒 Files selected for processing (5)
  • packages/cli/src/commands/context.ts
  • packages/cli/src/index.ts
  • packages/cli/src/internal/__tests__/context.test.ts
  • packages/cli/src/internal/context.ts
  • scripts/task-setup.sh

Comment thread packages/cli/src/index.ts
Comment thread scripts/task-setup.sh
Comment on lines +71 to +75
if (cd "$repo" && git show-ref --verify --quiet "refs/heads/$branch"); then
(cd "$repo" && git worktree add "$worktree_dir" "$branch")
else
(cd "$repo" && git worktree add "$worktree_dir" -b "$branch" origin/main)
fi
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify whether remote feat/* branches exist without local counterparts.
# If output is non-empty, current logic can create divergent branches.
git for-each-ref --format='%(refname:short)' refs/remotes/origin/feat/ \
| sed 's#^origin/##' \
| while read -r b; do
  git show-ref --verify --quiet "refs/heads/$b" || echo "$b"
done

Repository: lobu-ai/lobu

Length of output: 38


🏁 Script executed:

# Check if there are ANY remote branches
git branch -r | head -20

Repository: lobu-ai/lobu

Length of output: 99


🏁 Script executed:

# Check the context where this script is called and what branches are used
cd scripts && head -100 task-setup.sh && echo "---" && grep -n "branch=" task-setup.sh || true

Repository: lobu-ai/lobu

Length of output: 3561


🏁 Script executed:

# Verify the git worktree syntax for the proposed fix - test the command structure
git help worktree | grep -A 20 "worktree add" | head -30 || echo "Testing command syntax only"

Repository: lobu-ai/lobu

Length of output: 154


🏁 Script executed:

# Check if there are task branches or any naming conventions in use
git branch -a | grep -E "(task|feat)" | head -20 || echo "No matching branches found"

Repository: lobu-ai/lobu

Length of output: 38


Handle remote-only task branches when creating the worktree.

Line 71 checks only local refs/heads/$branch. If origin/$branch exists but local does not, line 74 creates a new branch from origin/main, diverging from existing task history. This occurs when a worktree is created on a fresh clone or after local branch deletion, while the remote branch already exists.

Note: The submodule setup logic (lines 79–88) correctly implements the intended pattern by checking remote branches before fallback, confirming this should apply to the main worktree as well.

Proposed fix
  if (cd "$repo" && git show-ref --verify --quiet "refs/heads/$branch"); then
    (cd "$repo" && git worktree add "$worktree_dir" "$branch")
+  elif (cd "$repo" && git show-ref --verify --quiet "refs/remotes/origin/$branch"); then
+    (cd "$repo" && git worktree add --track -b "$branch" "$worktree_dir" "origin/$branch")
  else
    (cd "$repo" && git worktree add "$worktree_dir" -b "$branch" origin/main)
  fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (cd "$repo" && git show-ref --verify --quiet "refs/heads/$branch"); then
(cd "$repo" && git worktree add "$worktree_dir" "$branch")
else
(cd "$repo" && git worktree add "$worktree_dir" -b "$branch" origin/main)
fi
if (cd "$repo" && git show-ref --verify --quiet "refs/heads/$branch"); then
(cd "$repo" && git worktree add "$worktree_dir" "$branch")
elif (cd "$repo" && git show-ref --verify --quiet "refs/remotes/origin/$branch"); then
(cd "$repo" && git worktree add --track -b "$branch" "$worktree_dir" "origin/$branch")
else
(cd "$repo" && git worktree add "$worktree_dir" -b "$branch" origin/main)
fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/task-setup.sh` around lines 71 - 75, The worktree creation currently
only checks local refs/heads/$branch before creating a new branch from
origin/main, which discards remote-only branch history; change the logic around
the git show-ref and git worktree add calls to also detect a remote branch
(refs/remotes/origin/$branch) and, if present, add the worktree from that remote
ref (use git worktree add "$worktree_dir" "origin/$branch") instead of creating
a new branch from origin/main; keep using the existing variables repo, branch
and worktree_dir and follow the same pattern used in the submodule logic (check
remote ref first, then fallback to creating from origin/main).

Comment thread scripts/task-setup.sh
Comment on lines +105 to +111
for env_local in "$repo"/.claude/worktrees/*/.env.local; do
[[ "$env_local" == "$worktree_dir/.env.local" ]] && continue
p="$(awk -F= '/^PORT=/{print $2; exit}' "$env_local" | tr -d '[:space:]')"
q="$(awk -F= '/^WORKER_PROXY_PORT=/{print $2; exit}' "$env_local" | tr -d '[:space:]')"
[[ -n "$p" && "$p" =~ ^[0-9]+$ && "$p" -gt "$highest_port" ]] && highest_port="$p"
[[ -n "$q" && "$q" =~ ^[0-9]+$ && "$q" -gt "$highest_proxy" ]] && highest_proxy="$q"
done
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Include root .env.local in the collision scan.

Line 105 scans only worktree .env.local files. If $repo/.env.local uses non-default ports, this allocator can still assign conflicting values.

Proposed fix
-shopt -s nullglob
-for env_local in "$repo"/.claude/worktrees/*/.env.local; do
+shopt -s nullglob
+for env_local in "$repo"/.env.local "$repo"/.claude/worktrees/*/.env.local; do
+  [[ -f "$env_local" ]] || continue
   [[ "$env_local" == "$worktree_dir/.env.local" ]] && continue
   p="$(awk -F= '/^PORT=/{print $2; exit}' "$env_local" | tr -d '[:space:]')"
   q="$(awk -F= '/^WORKER_PROXY_PORT=/{print $2; exit}' "$env_local" | tr -d '[:space:]')"
   [[ -n "$p" && "$p" =~ ^[0-9]+$ && "$p" -gt "$highest_port" ]] && highest_port="$p"
   [[ -n "$q" && "$q" =~ ^[0-9]+$ && "$q" -gt "$highest_proxy" ]] && highest_proxy="$q"
 done
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for env_local in "$repo"/.claude/worktrees/*/.env.local; do
[[ "$env_local" == "$worktree_dir/.env.local" ]] && continue
p="$(awk -F= '/^PORT=/{print $2; exit}' "$env_local" | tr -d '[:space:]')"
q="$(awk -F= '/^WORKER_PROXY_PORT=/{print $2; exit}' "$env_local" | tr -d '[:space:]')"
[[ -n "$p" && "$p" =~ ^[0-9]+$ && "$p" -gt "$highest_port" ]] && highest_port="$p"
[[ -n "$q" && "$q" =~ ^[0-9]+$ && "$q" -gt "$highest_proxy" ]] && highest_proxy="$q"
done
for env_local in "$repo"/.env.local "$repo"/.claude/worktrees/*/.env.local; do
[[ -f "$env_local" ]] || continue
[[ "$env_local" == "$worktree_dir/.env.local" ]] && continue
p="$(awk -F= '/^PORT=/{print $2; exit}' "$env_local" | tr -d '[:space:]')"
q="$(awk -F= '/^WORKER_PROXY_PORT=/{print $2; exit}' "$env_local" | tr -d '[:space:]')"
[[ -n "$p" && "$p" =~ ^[0-9]+$ && "$p" -gt "$highest_port" ]] && highest_port="$p"
[[ -n "$q" && "$q" =~ ^[0-9]+$ && "$q" -gt "$highest_proxy" ]] && highest_proxy="$q"
done
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/task-setup.sh` around lines 105 - 111, The loop that scans for
PORT/WORKER_PROXY_PORT only iterates worktree .env.local files (for env_local in
"$repo"/.claude/worktrees/*/.env.local) and therefore misses the repository root
.env.local; update the scan used by the function that computes highest_port and
highest_proxy so it also reads "$repo/.env.local" (either prepend a check for
"$repo/.env.local" before the for-loop or extend the glob to include it), parse
PORT and WORKER_PROXY_PORT the same way using the existing awk/tr -d logic, and
ensure you still skip the current worktree_dir/.env.local and update
highest_port and highest_proxy accordingly.

The previous commit relied on a per-user zsh function. Make this team-
friendly by exposing the workflow as Makefile targets the whole repo
can use, and add a cleanup path that takes the worktree down safely:

- `make task-setup NAME=<name>` — creates the paired worktree.
- `make task-clean NAME=<name> [FORCE=1]` — removes the worktree, both
  feat/<name> branches, and the Lobu CLI context. Refuses by default
  when there's uncommitted work or unpushed commits; FORCE=1 overrides.
- `lobu context rm <name>` — new CLI command + removeContext() helper.
  Idempotent on missing entries; refuses the default context.

The optional `task-start` / `task-resume` shell functions are still
documented in the script header for users who want one-command setup
+ exec claude — Make can't `cd` the parent shell, but a shell function
can.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
scripts/task-clean.sh (1)

23-28: ⚡ Quick win

Use explicit if for positional arg assignment (avoid && ... || ... control flow).

The current pattern on Line 27 is brittle Bash control flow and can trigger usage in edge failure cases. Prefer an explicit branch.

Proposed refactor
-    *) [[ -z "$name" ]] && name="$arg" || usage ;;
+    *)
+      if [[ -z "$name" ]]; then
+        name="$arg"
+      else
+        usage
+      fi
+      ;;
#!/usr/bin/env bash
# Verify this SC2015-style pattern appears only where expected.
rg -n '&&\s*[^;]+?\s*\|\|' scripts/task-clean.sh
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/task-clean.sh` around lines 23 - 28, The positional-arg assignment in
the for-loop uses the brittle `[[ -z "$name" ]] && name="$arg" || usage`
pattern; replace it with an explicit if/else branch so setting the variable
`name` is atomic and `usage` is only called on the actual else case — edit the
`for arg in "$@"` case arm (the `*)` branch) to use `if [[ -z "$name" ]]; then
name="$arg"; else usage; fi`, keeping the `--force|-f` and unknown-flag handling
unchanged and still calling the existing `usage` function when appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Makefile`:
- Around line 84-86: The Makefile's task-clean target currently treats any
non-empty FORCE as true and passes --force to ./scripts/task-clean.sh, which
lets values like FORCE=0 bypass safety; change the condition so --force is only
added when FORCE is explicitly "1" (i.e., test that $(FORCE) equals "1") when
invoking the script in the task-clean target to match documented behavior and
prevent unintended opt-in.

In `@scripts/task-clean.sh`:
- Around line 92-94: The script always appends --force to the git worktree
remove command (git -C "$repo" worktree remove "$worktree_dir" --force), which
defeats Git's safety checks; change the invocation to conditionally include
--force only when a cleanup-forced flag/variable is set (e.g., a parsed
FORCE_CLEANUP or --force option) so that by default it calls git -C "$repo"
worktree remove "$worktree_dir" and only uses --force when the explicit force
variable is true; update any option parsing to set that variable and reference
worktree_dir and repo when building the final command.

---

Nitpick comments:
In `@scripts/task-clean.sh`:
- Around line 23-28: The positional-arg assignment in the for-loop uses the
brittle `[[ -z "$name" ]] && name="$arg" || usage` pattern; replace it with an
explicit if/else branch so setting the variable `name` is atomic and `usage` is
only called on the actual else case — edit the `for arg in "$@"` case arm (the
`*)` branch) to use `if [[ -z "$name" ]]; then name="$arg"; else usage; fi`,
keeping the `--force|-f` and unknown-flag handling unchanged and still calling
the existing `usage` function when appropriate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 253122cc-757b-4789-ac15-57313412986b

📥 Commits

Reviewing files that changed from the base of the PR and between 2372770 and f802913.

📒 Files selected for processing (8)
  • Makefile
  • packages/cli/src/commands/context.ts
  • packages/cli/src/index.ts
  • packages/cli/src/internal/__tests__/context.test.ts
  • packages/cli/src/internal/context.ts
  • packages/cli/src/internal/index.ts
  • scripts/task-clean.sh
  • scripts/task-setup.sh
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/cli/src/commands/context.ts
  • packages/cli/src/internal/tests/context.test.ts
  • packages/cli/src/internal/context.ts

Comment thread Makefile Outdated
Comment thread scripts/task-clean.sh
Comment on lines +92 to +94
echo "→ removing worktree $worktree_dir"
git -C "$repo" worktree remove "$worktree_dir" --force

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

git worktree remove should respect the --force flag.

Line 93 always passes --force, which defeats Git’s native protection even when the user did not request force cleanup. This can delete work unexpectedly if earlier custom checks miss a state.

Proposed fix
 echo "→ removing worktree $worktree_dir"
-git -C "$repo" worktree remove "$worktree_dir" --force
+if [[ $force -eq 1 ]]; then
+  git -C "$repo" worktree remove "$worktree_dir" --force
+else
+  git -C "$repo" worktree remove "$worktree_dir"
+fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
echo "→ removing worktree $worktree_dir"
git -C "$repo" worktree remove "$worktree_dir" --force
echo "→ removing worktree $worktree_dir"
if [[ $force -eq 1 ]]; then
git -C "$repo" worktree remove "$worktree_dir" --force
else
git -C "$repo" worktree remove "$worktree_dir"
fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/task-clean.sh` around lines 92 - 94, The script always appends
--force to the git worktree remove command (git -C "$repo" worktree remove
"$worktree_dir" --force), which defeats Git's safety checks; change the
invocation to conditionally include --force only when a cleanup-forced
flag/variable is set (e.g., a parsed FORCE_CLEANUP or --force option) so that by
default it calls git -C "$repo" worktree remove "$worktree_dir" and only uses
--force when the explicit force variable is true; update any option parsing to
set that variable and reference worktree_dir and repo when building the final
command.

Pi review of #891 surfaced 7 real issues (no cosmetic). All addressed:

1. .task untracked → task-clean always refuses without FORCE=1.
   Fix: add `.task` to repo .gitignore so worktrees inherit the rule.

2. Submodule branch was created from origin/main, immediately dirtying
   the worktree when the parent pin and owletto's origin/main differ
   (today's reality). Fix: branch from HEAD (the just-checked-out
   submodule pin) instead.

3. `make task-clean FORCE=0` was treated as truthy ([ -n ] on any
   non-empty string). Fix: explicit `[ "$(FORCE)" = "1" ]`.

4. task-clean trusted potentially-stale origin refs when checking
   "ahead of remote". Fix: `git fetch origin --prune` first in both
   the parent and submodule repos.

5. `task-setup NAME=lobu` would have overwritten the default Lobu CLI
   context (and `lobu context rm` refuses the default, so cleanup
   couldn't recover). Fix at two layers:
   - task-setup.sh reserves names matching the built-in contexts
     (lobu, dev, local) and refuses early.
   - addContext() in the CLI refuses to overwrite the default
     context — protects any caller, not just the script.

6. `lobu context add --port` silently accepted malformed values
   ("abc" → no port; "8788abc" → 8788; "70000" → out of TCP range
   but persisted). Fix: parser validates `^\d+$` and 1..65535 range.

7. Tests: cover addContext refusing the default name.
   12/12 pass.
@buremba
Copy link
Copy Markdown
Member Author

buremba commented May 18, 2026

Pi review pass 1 — addressed (commit c716f24)

All 7 findings fixed with verifiable e2e:

# Finding Fix Verified
1 .task untracked → cleanup always refuses .task in .gitignore Fresh worktree off HEAD: git status --porcelain clean after writing .task
2 Submodule branched from origin/main (parent pin can differ → instant dirty worktree) Branch from submodule HEAD (the just-checked-out parent pin) scripts/task-setup.sh:90
3 make task-clean FORCE=0 falsely truthy ([ -n ] on any non-empty string) Explicit [ "$(FORCE)" = "1" ] FORCE=0 now refuses correctly; FORCE=1 still works
4 task-clean trusted potentially-stale origin refs when checking "ahead" git fetch origin --prune first, in both parent and submodule scripts/task-clean.sh:60-65
5 task-setup NAME=lobu would overwrite default Lobu context (cleanup refuses default, so unrecoverable) Two-layer: script reserves lobu/dev/local; addContext() refuses the default name task-setup lobu errors: "'lobu' is a reserved CLI context name"; addContext(DEFAULT_CONTEXT_NAME, ...) throws
6 --port accepted abc (NaN dropped silently), 8788abc (parsed as 8788), 70000 (out of TCP range) Parser validates ^\d+$ and 1..65535 All three reject with clear errors
7 No test for default-context overwrite Added test; 12/12 pass addContext refuses to overwrite the default context

bun run typecheck clean. Loss-mode reproducer + cleanup paths still pass end-to-end on the new commit.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
scripts/task-clean.sh (1)

100-101: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Honor the --force flag when removing the worktree.

Line 101 always forces removal, so non-force mode still bypasses Git’s built-in protection.

Proposed fix
 echo "→ removing worktree $worktree_dir"
-git -C "$repo" worktree remove "$worktree_dir" --force
+if [[ $force -eq 1 ]]; then
+  git -C "$repo" worktree remove "$worktree_dir" --force
+else
+  git -C "$repo" worktree remove "$worktree_dir"
+fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/task-clean.sh` around lines 100 - 101, The git worktree removal
currently always passes --force (the line git -C "$repo" worktree remove
"$worktree_dir" --force), which ignores the intended non-force behavior; change
this so --force is only appended when the script's force flag/variable (e.g.,
FORCE or force_flag) is true/Set, otherwise call git -C "$repo" worktree remove
"$worktree_dir" without --force; implement this conditional append near the
existing worktree removal call so the removal honors the passed flag.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@scripts/task-clean.sh`:
- Around line 100-101: The git worktree removal currently always passes --force
(the line git -C "$repo" worktree remove "$worktree_dir" --force), which ignores
the intended non-force behavior; change this so --force is only appended when
the script's force flag/variable (e.g., FORCE or force_flag) is true/Set,
otherwise call git -C "$repo" worktree remove "$worktree_dir" without --force;
implement this conditional append near the existing worktree removal call so the
removal honors the passed flag.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a57e75f0-804b-4a8c-82e7-1a1f34fce639

📥 Commits

Reviewing files that changed from the base of the PR and between f802913 and c716f24.

📒 Files selected for processing (7)
  • .gitignore
  • Makefile
  • packages/cli/src/index.ts
  • packages/cli/src/internal/__tests__/context.test.ts
  • packages/cli/src/internal/context.ts
  • scripts/task-clean.sh
  • scripts/task-setup.sh
✅ Files skipped from review due to trivial changes (1)
  • .gitignore
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/cli/src/internal/context.ts
  • Makefile
  • packages/cli/src/index.ts
  • scripts/task-setup.sh

buremba added 2 commits May 18, 2026 21:04
Lets external tools that hardcode a path (Chrome's "Load unpacked" /
Xcode opening the Mac app) follow whichever worktree is currently
active, without manually re-pointing them.

- `scripts/task-use.sh <name|main>` retargets two fixed symlinks:
    ~/.config/lobu-dev/active/chrome → <root>/packages/owletto/apps/chrome
    ~/.config/lobu-dev/active/mac    → <root>/packages/owletto/apps/mac
  Chrome's Load-unpacked / Xcode's Open are pointed at the symlinks
  permanently; `task-use` swaps which worktree they resolve to.
- `task-setup` auto-activates each new worktree it creates.
- `task-clean` of the *active* worktree falls back to `main` so the
  symlinks never dangle into a deleted directory.
- Records the active name at ~/.config/lobu-dev/active/.active-name.
- New Makefile target: `make task-use NAME=<name|main>`.

Scoped to two specific consumer dirs intentionally — no
~/.config/lobu/worktree-targets.json registry yet. When a third
consumer appears, revisit the registry design.

Verified end-to-end: setup auto-activates, swap to main, swap back,
clean-of-active falls back to main with no dangling links.
Pi second-pass review noted that Chrome's MV3 extension does not auto-
reload when the "Load unpacked" symlink retargets — the user must click
Reload in chrome://extensions. Without the reminder it looks like the
worktree swap didn't take effect.

No code path change; only a multi-line note appended to task-use.sh's
success output.

Pi verdict (verbatim summary):
- Option B (symlink swap, no registry) is sound for this stage.
- Registry would be overengineering at 2 hardcoded entries; revisit
  when a third real consumer appears.
- LobuServerConfig.cwd is the right layer for the dev server (no
  symlink needed); caveat: running servers must be restarted after
  cwd/config change.
- Sharper-name suggestion (e.g. owletto-chrome-ext over bare chrome)
  is nice-to-have — deferred.
@buremba buremba merged commit a8e4a35 into main May 18, 2026
23 checks passed
@buremba buremba deleted the feat/task-setup-script branch May 18, 2026 20:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants