@clipboard-health/groundcrew 4.45.5 → 4.45.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"cmuxAdapter.d.ts","sourceRoot":"","sources":["../../src/lib/cmuxAdapter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,KAAK,OAAO,EAIb,MAAM,uBAAuB,CAAC;AAG/B,eAAO,MAAM,WAAW,EAAE,OA+EzB,CAAC"}
1
+ {"version":3,"file":"cmuxAdapter.d.ts","sourceRoot":"","sources":["../../src/lib/cmuxAdapter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,KAAK,OAAO,EAIb,MAAM,uBAAuB,CAAC;AAG/B,eAAO,MAAM,WAAW,EAAE,OAqFzB,CAAC"}
@@ -7,18 +7,25 @@ import { isSignalAborted, runWorkspaceCommand, } from "./workspaceAdapter.js";
7
7
  import { debug, errorMessage, log } from "./util.js";
8
8
  export const cmuxAdapter = {
9
9
  async open(spec, signal) {
10
- const output = await runWorkspaceCommand("cmux", [
11
- "--json",
12
- "new-workspace",
13
- "--name",
14
- spec.name,
15
- "--cwd",
16
- spec.cwd,
17
- "--command",
18
- spec.command,
19
- "--description",
20
- cmuxDescriptionFor(spec.name),
21
- ], signal);
10
+ let output;
11
+ try {
12
+ output = await runWorkspaceCommand("cmux", [
13
+ "--json",
14
+ "new-workspace",
15
+ "--name",
16
+ spec.name,
17
+ "--cwd",
18
+ spec.cwd,
19
+ "--command",
20
+ spec.command,
21
+ "--description",
22
+ cmuxDescriptionFor(spec.name),
23
+ ], signal);
24
+ }
25
+ catch (error) {
26
+ await closeWorkspaceLeakedByFailedOpen(error, spec.name, signal);
27
+ throw error;
28
+ }
22
29
  const workspaceId = extractCmuxOpenId(output);
23
30
  if (workspaceId === undefined) {
24
31
  log(`cmux new-workspace returned unrecognized output for ${spec.name}; if a workspace was created, run \`cmux close-workspace\` manually.`);
@@ -180,6 +187,48 @@ async function applyCmuxStatus(workspaceId, status, signal) {
180
187
  async function closeCmuxWorkspace(workspaceId, signal) {
181
188
  await runWorkspaceCommand("cmux", ["close-workspace", "--workspace", workspaceId], signal);
182
189
  }
190
+ /**
191
+ * cmux occasionally exits non-zero from `new-workspace` while still having
192
+ * created the workspace (a known flake where it also lands in the wrong `--cwd`).
193
+ * The id rides along in the failed command's captured output, so recover it and
194
+ * close that exact workspace by id — a failed launch must not strand an orphan
195
+ * tagged with the task's `groundcrew:<taskId>` marker. Closing by the recovered
196
+ * id needs no `list-workspaces`, so it survives a concurrent list failure that
197
+ * would defeat re-enumeration. Re-enumeration close is unsafe here anyway; we
198
+ * hold the precise id cmux returned, so there is no same-named-sibling risk.
199
+ */
200
+ async function closeWorkspaceLeakedByFailedOpen(error, name, signal) {
201
+ if (isSignalAborted(signal)) {
202
+ return;
203
+ }
204
+ const workspaceId = extractCmuxOpenIdFromFailure(error);
205
+ if (workspaceId === undefined) {
206
+ return;
207
+ }
208
+ try {
209
+ await closeCmuxWorkspace(workspaceId, signal);
210
+ debug(`cmux new-workspace for ${name} exited non-zero but had created ${workspaceId}; closed the leaked workspace.`);
211
+ }
212
+ catch (closeError) {
213
+ log(`cmux new-workspace for ${name} exited non-zero and left workspace ${workspaceId}; automatic close failed (${errorMessage(closeError)}). Run \`cmux close-workspace --workspace ${workspaceId}\` by hand.`);
214
+ }
215
+ }
216
+ /**
217
+ * Recover the created workspace id from a failed `new-workspace`. Parse only
218
+ * the captured stdout slice of the command error's message (see
219
+ * `normalizeCommandError`), never the whole message — matching against stderr
220
+ * or the echoed command line risks grabbing an unrelated `workspace:N` and
221
+ * closing the wrong workspace. The slice is the same shape the success path
222
+ * sees, so `extractCmuxOpenId` handles both the `--json` id object and a bare
223
+ * `workspace:N` ref.
224
+ */
225
+ function extractCmuxOpenIdFromFailure(error) {
226
+ const stdout = cmuxStdoutFromFailureMessage(errorMessage(error));
227
+ return stdout === undefined ? undefined : extractCmuxOpenId(stdout);
228
+ }
229
+ function cmuxStdoutFromFailureMessage(message) {
230
+ return /\nStdout:\n([\s\S]*?)(?:\nCause: |$)/.exec(message)?.[1];
231
+ }
183
232
  function isCmuxSetStatusUnsupported(error) {
184
233
  return errorMessage(error).includes('unknown command "set-status"');
185
234
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clipboard-health/groundcrew",
3
- "version": "4.45.5",
3
+ "version": "4.45.6",
4
4
  "description": "Linear-driven orchestrator that launches AI coding agents in git worktrees, with workspace lifecycle and usage tracking.",
5
5
  "keywords": [
6
6
  "agent",