CB-507: fix NPE on a worktree spawn with no cwd (HTTP 500 over REST)
POST /workers?worktree=true returned HTTP 500 with a NullPointerException out of ProcessBuilder.start(): acquireWithWorktree resolved the repo root from firstNonBlank(requestedCwd, callerCwd), and a plain REST spawn supplies neither (BridgedApp hardcodes callerCwd=null, "no MCP caller over REST"). Both null yielded null, putting `git -C null rev-parse --show-toplevel` on the command line. Now resolved through launcher.effectiveCwd, the CB-112 chain used everywhere else (requested -> profile cwd -> caller -> daemon cwd -> "."), which is documented never to return null. The non-worktree path in this same class already went through it; only the worktree branch was missed. Also fixes a second latent bug in the same line: firstNonBlank never consulted the profile's configured cwd:, so a worktree spawn silently ignored a pinned per-profile working directory. effectiveCwd honours it. Removes firstNonBlank, now dead (this was its only call site) — javac ignores an unused private method but IDE inspections flag it, and CLAUDE.md requires a clean bill. Why 311 tests missed it: the null/null case only arises over REST, and WorktreeSessionManagerTest always passes an explicit cwd. Over MCP callerCwd is populated from the caller PID, so the feature worked there. This is the third REST-vs-MCP divergence found this month, after CB-505's path-trusted session id. The one-line change was implemented by an opencode-free worker over the bridge in an isolated worktree (branch worker/cb-507-worktree-cwd-npe-3e9c3b-3); the dead-helper cleanup and the explanatory comment were added on integration. A regression test is still outstanding and is being delegated separately.
This commit is contained in:
@@ -147,7 +147,13 @@ public final class SessionManager implements TurnListener {
|
||||
String ownerTerminal, WorktreeRequest wt) {
|
||||
String resolvedProfile = (profile == null || profile.isBlank())
|
||||
? launcher.defaultProfile() : profile;
|
||||
String repoRoot = worktrees.repoRoot(firstNonBlank(requestedCwd, callerCwd));
|
||||
// CB-507: resolve through the launcher's CB-112 chain (requested → profile cwd → caller →
|
||||
// daemon cwd → "."), never the raw args. A plain REST spawn supplies neither a requested
|
||||
// nor a caller cwd, so taking the first non-blank of those two yielded null and put
|
||||
// `git -C null` on the command line — an NPE out of ProcessBuilder, surfacing as HTTP 500.
|
||||
// The non-worktree path always used this chain; only this branch was missed.
|
||||
String repoRoot = worktrees.repoRoot(
|
||||
launcher.effectiveCwd(new SpawnRequest(resolvedProfile, requestedCwd, callerCwd)));
|
||||
String branch = "worker/" + slug(wt.ticketSlug()) + "-" + nonce();
|
||||
String path = null;
|
||||
PeerHandle handle;
|
||||
@@ -192,13 +198,6 @@ public final class SessionManager implements TurnListener {
|
||||
return String.format("%06x", nonceRandom.nextInt(1 << 24)) + "-" + nonceSeq.incrementAndGet();
|
||||
}
|
||||
|
||||
private static String firstNonBlank(String... values) {
|
||||
for (String v : values) {
|
||||
if (v != null && !v.isBlank()) return v;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the old session and acquire a fresh one with the same profile and working directory.
|
||||
* The new session is guaranteed to have a pane id distinct from the old one (no-reuse invariant).
|
||||
|
||||
Reference in New Issue
Block a user