@brutalsystems/muster 3.3.2 → 3.3.3

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.
package/dist/help.js CHANGED
@@ -138,7 +138,7 @@ export const COMMANDS = [
138
138
  ["--format FORMAT", "json (default) or human"],
139
139
  ],
140
140
  notes: [
141
- "Prints the final record, with state, exit_code and signal (null for a session, whose terminal keeps no exit status). Exits 0 when it has ended, 2 for an id that names nothing, and 124 on timeout, printing the record as it stands.",
141
+ "Prints the final record, with state, exit_code and signal (null for a session, whose terminal keeps no exit status). Exits 0 when it has ended, 2 for an id that names nothing, 124 on timeout, printing the record as it stands, and 75 when the registry is locked (as every command does): retry.",
142
142
  ],
143
143
  examples: [
144
144
  "muster wait 6fae755b-8f5d-4425-8ed7-69e84bfac8c5",
package/dist/muster.js CHANGED
@@ -6,6 +6,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
6
6
  import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
7
7
  import { z } from "zod";
8
8
  import { Muster, UnknownRun, durableId, failedLaunchId } from "./run.js";
9
+ import { RegistryLocked } from "./registry.js";
9
10
  import { waitSeconds } from "./duration.js";
10
11
  import { delay } from "./identity/processes.js";
11
12
  import { formatHuman, humanColorEnabled, statusText } from "./format.js";
@@ -486,5 +487,5 @@ main().catch((e) => {
486
487
  ? `\n[muster] see \`muster ${command} --help\` for its options`
487
488
  : "";
488
489
  process.stderr.write(`[muster] ${e.message}${hint}\n`);
489
- process.exitCode = 1;
490
+ process.exitCode = e instanceof RegistryLocked ? RegistryLocked.exitCode : 1;
490
491
  });
package/dist/registry.js CHANGED
@@ -38,12 +38,25 @@ export class IdempotencyKeyReuse extends Error {
38
38
  }
39
39
  }
40
40
  /**
41
- * A lock directory with no owner file is presumed live for this long. Long
42
- * enough that no transaction comes near it, and the holder writes its owner
43
- * file immediately after taking the lock, so in practice only a lock left by a
44
- * version that wrote none (an empty directory) ever reaches it.
41
+ * A lock directory with no owner file is presumed live for this long. A
42
+ * transaction holds the lock for milliseconds and the holder writes its owner
43
+ * file immediately after taking it, so a lock still unowned this long was left
44
+ * by a holder killed in between, or by a version that wrote none (#64). Three
45
+ * default lock waits: one waiter may still time out on it, the next breaks it.
45
46
  */
46
- const UNOWNED_LOCK_MS = 60_000;
47
+ const UNOWNED_LOCK_MS = 15_000;
48
+ /**
49
+ * The registry is busy, not broken and not empty: the CLI exits 75
50
+ * (EX_TEMPFAIL, "try again later") for it, so a script polling `list` or
51
+ * `wait` can tell it from a run that is gone (#64) and retry.
52
+ */
53
+ export class RegistryLocked extends Error {
54
+ static exitCode = 75;
55
+ constructor(message) {
56
+ super(message);
57
+ this.name = "RegistryLocked";
58
+ }
59
+ }
47
60
  let self;
48
61
  /** Statuses whose process is over: such an entry no longer holds its id. */
49
62
  const FINISHED = ["exited", "failed", "stopped"];
@@ -84,9 +97,14 @@ export class Registry {
84
97
  continue;
85
98
  if (Date.now() > deadline) {
86
99
  const owner = await lockOwner(lock);
87
- throw new Error(owner
88
- ? `Registry locked, held by pid ${owner.pid}; verify no Muster operation is running before removing registry.lock`
89
- : "Registry locked; verify no Muster operation is running before removing registry.lock");
100
+ const age = await ageMs(lock);
101
+ const held = [
102
+ owner ? `held by pid ${owner.pid}` : "with no owner recorded",
103
+ age === undefined ? undefined : `for ${Math.round(age / 1000)}s`,
104
+ ]
105
+ .filter(Boolean)
106
+ .join(" ");
107
+ throw new RegistryLocked(`Registry locked, ${held}; retry shortly. If it persists, verify no Muster operation is running before removing ${lock}`);
90
108
  }
91
109
  await delay(20);
92
110
  }
package/dist/run.js CHANGED
@@ -11,7 +11,7 @@ import { assertEnforcementMeetsMinimum, assertLevelWithinCeiling, assertMayAct,
11
11
  import { IdentityError, readIdentity, } from "./identity-store.js";
12
12
  import { resolveCredential } from "./identity-token.js";
13
13
  import { copyIdentity, identityEnv } from "./identity-copy.js";
14
- import { currentEntry, Registry } from "./registry.js";
14
+ import { currentEntry, Registry, RegistryLocked, } from "./registry.js";
15
15
  import { LaunchLog } from "./log.js";
16
16
  import { hosts, selectHost } from "./hosts/index.js";
17
17
  import { AGENTS } from "./agents.js";
@@ -1470,6 +1470,10 @@ export class Muster {
1470
1470
  return await this.find(id);
1471
1471
  }
1472
1472
  catch (e) {
1473
+ // Busy is not gone: `wait` exits 2 for an UnknownRun, and a poller that
1474
+ // read a lock as "no such run" is how #64 ended a wait loop early.
1475
+ if (e instanceof RegistryLocked)
1476
+ throw e;
1473
1477
  const message = e.message;
1474
1478
  // The resolver's "unknown: <every live name>" reads as a list of
1475
1479
  // matches; the ambiguous case does want its candidates, so it keeps them.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brutalsystems/muster",
3
- "version": "3.3.2",
3
+ "version": "3.3.3",
4
4
  "description": "Launch instructed, reachable local agent sessions",
5
5
  "type": "module",
6
6
  "engines": {