mustard-claude 3.1.8 → 3.1.11
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/package.json
CHANGED
package/templates/CLAUDE.md
CHANGED
|
@@ -55,7 +55,7 @@ node scripts/sync-registry.js --force
|
|
|
55
55
|
- PreToolUse hooks use `permissionDecision` response format
|
|
56
56
|
- PostToolUse hooks use `decision` response format
|
|
57
57
|
- Every new hook must be registered in `settings.json` with a timeout
|
|
58
|
-
- Task dispatch failures (API overload) are logged to `pipeline-state.lastDispatchFailure`; `/resume` auto-recovers within 10 min
|
|
58
|
+
- Task dispatch failures (API overload, HTTP 5xx, tool result missing) are logged to `pipeline-state.lastDispatchFailure`; `/resume` auto-recovers within 10 min
|
|
59
59
|
- Generated files must start with `<!-- mustard:generated -->` header
|
|
60
60
|
- Skills must have YAML frontmatter BEFORE the `<!-- mustard:generated -->` line
|
|
61
61
|
|
|
@@ -69,8 +69,8 @@ Before the normal detect-and-confirm flow, scan the newest pipeline state for a
|
|
|
69
69
|
## Context
|
|
70
70
|
- Branch: {from git}
|
|
71
71
|
- Files changed: {run `node .claude/scripts/diff-context.js`}
|
|
72
|
-
- Last agent: {
|
|
73
|
-
- Last action: {
|
|
72
|
+
- Last agent: {Read `.claude/.agent-memory/_index.json` and pick the last entry's `agent_type`. If the file or `.agent-memory/` directory is missing, print literal `(none)` — do NOT probe with `ls`/`grep`, it surfaces noisy exit codes}
|
|
73
|
+
- Last action: {from the same last entry's `summary` field. If missing, print literal `(no prior memory)`}
|
|
74
74
|
- Decisions: {decisions[] from pipeline state, if any}
|
|
75
75
|
|
|
76
76
|
## Next Action
|
|
@@ -554,7 +554,7 @@ describe("subagent-tracker.js overload detection", () => {
|
|
|
554
554
|
assert.equal(r.code, 0);
|
|
555
555
|
const state = JSON.parse(fs.readFileSync(pipelinePath, "utf8"));
|
|
556
556
|
assert.ok(state.lastDispatchFailure, "flag must be set");
|
|
557
|
-
assert.equal(state.lastDispatchFailure.reason, "
|
|
557
|
+
assert.equal(state.lastDispatchFailure.reason, "dispatch_failure");
|
|
558
558
|
assert.equal(state.lastDispatchFailure.agentType, "general-purpose");
|
|
559
559
|
assert.equal(state.lastDispatchFailure.description, "test dispatch");
|
|
560
560
|
} finally {
|
|
@@ -562,6 +562,40 @@ describe("subagent-tracker.js overload detection", () => {
|
|
|
562
562
|
}
|
|
563
563
|
});
|
|
564
564
|
|
|
565
|
+
it("should flag lastDispatchFailure on tool result missing infrastructure error", async () => {
|
|
566
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "infra-missing-"));
|
|
567
|
+
const pipelinePath = setupPipelineState(tmpDir);
|
|
568
|
+
try {
|
|
569
|
+
const r = await dispatchTaskResult(tmpDir, {
|
|
570
|
+
is_error: true,
|
|
571
|
+
content: "Tool result missing due to internal error",
|
|
572
|
+
});
|
|
573
|
+
assert.equal(r.code, 0);
|
|
574
|
+
const state = JSON.parse(fs.readFileSync(pipelinePath, "utf8"));
|
|
575
|
+
assert.ok(state.lastDispatchFailure, "flag must be set on infra failure");
|
|
576
|
+
assert.equal(state.lastDispatchFailure.reason, "dispatch_failure");
|
|
577
|
+
} finally {
|
|
578
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
it("should flag lastDispatchFailure on HTTP 503 service unavailable", async () => {
|
|
583
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "infra-503-"));
|
|
584
|
+
const pipelinePath = setupPipelineState(tmpDir);
|
|
585
|
+
try {
|
|
586
|
+
const r = await dispatchTaskResult(tmpDir, {
|
|
587
|
+
is_error: true,
|
|
588
|
+
content: "Error 503: service unavailable",
|
|
589
|
+
});
|
|
590
|
+
assert.equal(r.code, 0);
|
|
591
|
+
const state = JSON.parse(fs.readFileSync(pipelinePath, "utf8"));
|
|
592
|
+
assert.ok(state.lastDispatchFailure, "flag must be set on 5xx");
|
|
593
|
+
assert.equal(state.lastDispatchFailure.reason, "dispatch_failure");
|
|
594
|
+
} finally {
|
|
595
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
|
|
565
599
|
it("should NOT flag on happy-path agent that merely documents rate limiting", async () => {
|
|
566
600
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "overload-docs-"));
|
|
567
601
|
const pipelinePath = setupPipelineState(tmpDir);
|
|
@@ -72,9 +72,10 @@ function isRtkAvailable() {
|
|
|
72
72
|
*/
|
|
73
73
|
function rtkRewrite(cmd) {
|
|
74
74
|
try {
|
|
75
|
-
// rtk rewrite expects the raw command as
|
|
76
|
-
//
|
|
77
|
-
|
|
75
|
+
// rtk rewrite expects the raw command as a single argv element.
|
|
76
|
+
// Using execFileSync avoids shell re-parsing, which would strip quotes
|
|
77
|
+
// and corrupt regex patterns containing brackets (e.g. grep '[x]').
|
|
78
|
+
const result = execFileSync('rtk', ['rewrite', cmd], {
|
|
78
79
|
encoding: 'utf8',
|
|
79
80
|
stdio: ['pipe', 'pipe', 'ignore'], // ignore stderr
|
|
80
81
|
timeout: 3000,
|
|
@@ -195,15 +195,18 @@ function handlePostToolUse(data, stateDir) {
|
|
|
195
195
|
|
|
196
196
|
const toolResponse = data.tool_response || {};
|
|
197
197
|
const responseText = JSON.stringify(toolResponse).toLowerCase();
|
|
198
|
-
// Detect
|
|
199
|
-
// this on Task tool failures) AND at least one
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
|
|
198
|
+
// Detect dispatch failures conservatively: require is_error=true (Claude
|
|
199
|
+
// Code sets this on Task tool failures) AND at least one failure keyword.
|
|
200
|
+
// Covers:
|
|
201
|
+
// - API overload / rate limiting (429, 529, throttle, too many requests)
|
|
202
|
+
// - Infrastructure errors (tool result missing, HTTP 5xx, service unavailable)
|
|
203
|
+
// The regex avoids false positives on agents that merely *document* error
|
|
204
|
+
// handling in their returned content (see "unrelated error" test below).
|
|
205
|
+
const isDispatchFailure =
|
|
203
206
|
toolResponse.is_error === true &&
|
|
204
|
-
/overload|rate.?limit|\b429\b|\b529\b|throttl|too many requests/.test(responseText);
|
|
207
|
+
/overload|rate.?limit|\b429\b|\b529\b|throttl|too many requests|tool result missing|\b50[0-4]\b|service unavailable/.test(responseText);
|
|
205
208
|
|
|
206
|
-
if (!
|
|
209
|
+
if (!isDispatchFailure) return;
|
|
207
210
|
|
|
208
211
|
const projectDir = path.resolve(stateDir, '..', '..');
|
|
209
212
|
const statesDir = path.join(projectDir, '.claude', '.pipeline-states');
|
|
@@ -231,7 +234,7 @@ function handlePostToolUse(data, stateDir) {
|
|
|
231
234
|
const state = JSON.parse(fs.readFileSync(newest, 'utf8'));
|
|
232
235
|
state.lastDispatchFailure = {
|
|
233
236
|
at: new Date().toISOString(),
|
|
234
|
-
reason: '
|
|
237
|
+
reason: 'dispatch_failure',
|
|
235
238
|
agentType: toolInput.subagent_type || 'unknown',
|
|
236
239
|
description: toolInput.description || '',
|
|
237
240
|
prompt: (toolInput.prompt || '').slice(0, 2000),
|