fix: prevent bare /gsd from stealing session lock from running auto-mode (#1507) (#1517)

Bare /gsd and /gsd next now check for a remote auto-mode session via
readSessionLockData before attempting to start step-mode. If another
process holds the lock, a steering menu is shown instead of competing
for the lock and killing the running session.

Also fixes the guided-flow "all slices discussed" message to detect
active auto-mode and direct users to /gsd status instead of bare /gsd.

Closes #1507

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
TÂCHES 2026-03-19 17:59:10 -06:00 committed by GitHub
parent c2d46a951b
commit eb2939760f
2 changed files with 43 additions and 2 deletions

View file

@ -48,6 +48,7 @@ import { computeProgressScore, formatProgressLine } from "./progress-score.js";
import { runEnvironmentChecks } from "./doctor-environment.js";
import { handleLogs } from "./commands-logs.js";
import { handleStart, handleTemplates, getTemplateCompletions } from "./commands-workflow-templates.js";
import { readSessionLockData, isSessionLockProcessAlive } from "./session-lock.js";
/** Resolve the effective project root, accounting for worktree paths. */
@ -69,6 +70,39 @@ export function projectRoot(): string {
return root;
}
/**
* Check if another process holds the auto-mode session lock.
* Returns the lock data if a remote session is alive, null otherwise.
*/
function getRemoteAutoSession(basePath: string): { pid: number } | null {
const lockData = readSessionLockData(basePath);
if (!lockData) return null;
if (lockData.pid === process.pid) return null;
if (!isSessionLockProcessAlive(lockData)) return null;
return { pid: lockData.pid };
}
/**
* Show a steering menu when auto-mode is running in another process.
* Returns true if a remote session was detected (caller should return early).
*/
function notifyRemoteAutoActive(ctx: ExtensionCommandContext, basePath: string): boolean {
const remote = getRemoteAutoSession(basePath);
if (!remote) return false;
ctx.ui.notify(
`Auto-mode is running in another process (PID ${remote.pid}).\n` +
`Use these commands to interact with it:\n` +
` /gsd status — check progress\n` +
` /gsd discuss — discuss architecture decisions\n` +
` /gsd queue — queue the next milestone\n` +
` /gsd steer — apply an override to active work\n` +
` /gsd capture — fire-and-forget thought\n` +
` /gsd stop — stop auto-mode`,
"warning",
);
return true;
}
export function registerGSDCommand(pi: ExtensionAPI): void {
pi.registerCommand("gsd", {
description: "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|visualize|queue|quick|capture|triage|dispatch|history|undo|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|update",
@ -512,6 +546,7 @@ export async function handleGSDCommand(
await handleDryRun(ctx, projectRoot());
return;
}
if (notifyRemoteAutoActive(ctx, projectRoot())) return;
const verboseMode = trimmed.includes("--verbose");
const debugMode = trimmed.includes("--debug");
if (debugMode) enableDebug(projectRoot());
@ -906,7 +941,7 @@ Examples:
}
if (trimmed === "") {
// Bare /gsd defaults to step mode
if (notifyRemoteAutoActive(ctx, projectRoot())) return;
await startAuto(ctx, pi, projectRoot(), false, { step: true });
return;
}

View file

@ -23,6 +23,7 @@ import {
} from "./paths.js";
import { join } from "node:path";
import { readFileSync, existsSync, mkdirSync, readdirSync, rmSync, unlinkSync } from "node:fs";
import { readSessionLockData, isSessionLockProcessAlive } from "./session-lock.js";
import { nativeIsRepo, nativeInit } from "./native-git-bridge.js";
import { ensureGitignore, ensurePreferences, untrackRuntimeFiles } from "./gitignore.js";
import { loadEffectiveGSDPreferences } from "./preferences.js";
@ -516,8 +517,13 @@ export async function showDiscuss(
// If all pending slices are discussed, notify and exit instead of looping
const allDiscussed = pendingSlices.every(s => discussedMap.get(s.id));
if (allDiscussed) {
const lockData = readSessionLockData(basePath);
const remoteAutoRunning = lockData && lockData.pid !== process.pid && isSessionLockProcessAlive(lockData);
const nextStep = remoteAutoRunning
? "Auto-mode is already running — use /gsd status to check progress."
: "Run /gsd to start planning.";
ctx.ui.notify(
`All ${pendingSlices.length} slices discussed. Run /gsd to start planning.`,
`All ${pendingSlices.length} slices discussed. ${nextStep}`,
"info",
);
return;