feat(lanes): let claude and codex lanes coexist at the same cwd
Repo/Adopt mode (POST /api/lanes/ensure) treated lanes.cwd as globally unique, so adopting a path already driven by one CLI silently ignored a different tool choice. Uniqueness moves to the composite (cwd, tool): one directory can now back a claude lane and a codex lane side by side. - server/db.js: drop the column-level UNIQUE on cwd, migrate existing DBs via table rebuild, add idx_lanes_cwd_tool. - resolveLaneByCwd gains an optional tool filter; hook ingestion always pins to "claude" since Codex has no hook mechanism here, so a hook event never touches a codex lane sharing the path. - POST /ensure resolves the existing owner by (cwd, tool) instead of cwd alone, so a different tool creates a sibling lane. - LaneConsolePane's cwd-only lane lookups now also match tool, so a shared cwd doesn't attach a run to the wrong sibling lane. Worktree mode is unaffected (already allocates a unique directory per lane). docs/LANES.md and CLAUDE.md updated to describe the new composite uniqueness.
This commit is contained in:
+69
-1
@@ -414,7 +414,7 @@ db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS lanes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL DEFAULT '',
|
||||
cwd TEXT NOT NULL UNIQUE,
|
||||
cwd TEXT NOT NULL,
|
||||
branch TEXT,
|
||||
pipeline TEXT NOT NULL DEFAULT 'default',
|
||||
session_id TEXT,
|
||||
@@ -585,6 +585,74 @@ try {
|
||||
).run();
|
||||
}
|
||||
|
||||
// Migrate: cwd used to be single-column UNIQUE (one directory = one lane).
|
||||
// Codex and Claude Code lanes now coexist at the same adopted path, so
|
||||
// uniqueness moves to (cwd, tool) instead. SQLite can't ALTER TABLE to drop a
|
||||
// column-level UNIQUE, so rebuild the table when the old constraint is still
|
||||
// there — same rename-copy-drop pattern as the `agents` status rebuild above.
|
||||
// A fresh DB already gets the new column definition from CREATE TABLE.
|
||||
{
|
||||
const laneTableInfo = db
|
||||
.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='lanes'")
|
||||
.get();
|
||||
if (
|
||||
laneTableInfo &&
|
||||
laneTableInfo.sql &&
|
||||
laneTableInfo.sql.includes("cwd TEXT NOT NULL UNIQUE")
|
||||
) {
|
||||
db.exec(`
|
||||
PRAGMA foreign_keys = OFF;
|
||||
BEGIN;
|
||||
CREATE TABLE lanes_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL DEFAULT '',
|
||||
cwd TEXT NOT NULL,
|
||||
branch TEXT,
|
||||
pipeline TEXT NOT NULL DEFAULT 'default',
|
||||
session_id TEXT,
|
||||
run_id TEXT,
|
||||
stage TEXT NOT NULL DEFAULT 'idle',
|
||||
stage_since TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'idle',
|
||||
gate_decision TEXT,
|
||||
ci_status TEXT,
|
||||
needs_action TEXT,
|
||||
links TEXT NOT NULL DEFAULT '{}',
|
||||
stages TEXT NOT NULL DEFAULT '{}',
|
||||
notes TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||
kind TEXT NOT NULL DEFAULT 'adopted',
|
||||
source_repo TEXT,
|
||||
base_branch TEXT,
|
||||
slug TEXT,
|
||||
tool TEXT NOT NULL DEFAULT 'claude',
|
||||
detected_stage TEXT,
|
||||
detected_signal TEXT,
|
||||
detected_at TEXT,
|
||||
slot INTEGER,
|
||||
ports TEXT NOT NULL DEFAULT '{}',
|
||||
active_feature_id INTEGER REFERENCES lane_features(id) ON DELETE SET NULL
|
||||
);
|
||||
INSERT INTO lanes_new SELECT
|
||||
id, title, cwd, branch, pipeline, session_id, run_id, stage, stage_since,
|
||||
status, gate_decision, ci_status, needs_action, links, stages, notes,
|
||||
created_at, updated_at, kind, source_repo, base_branch, slug, tool,
|
||||
detected_stage, detected_signal, detected_at, slot, ports, active_feature_id
|
||||
FROM lanes;
|
||||
DROP TABLE lanes;
|
||||
ALTER TABLE lanes_new RENAME TO lanes;
|
||||
COMMIT;
|
||||
PRAGMA foreign_keys = ON;
|
||||
`);
|
||||
db.exec(`
|
||||
CREATE INDEX IF NOT EXISTS idx_lanes_session ON lanes(session_id);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_lanes_slot ON lanes(slot) WHERE slot IS NOT NULL;
|
||||
`);
|
||||
}
|
||||
}
|
||||
db.prepare("CREATE UNIQUE INDEX IF NOT EXISTS idx_lanes_cwd_tool ON lanes(cwd, tool)").run();
|
||||
|
||||
// Migrate: link agent rows to a workflow run. Workflow inner-agents are already
|
||||
// ingested as subagents (same subagents/ dir); these columns add the grouping +
|
||||
// phase that the run journal provides. Additive, safe on existing DBs.
|
||||
|
||||
Reference in New Issue
Block a user