diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8c0f45586..3ce542edf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,6 +23,7 @@
- iMessage: fix reasoning persistence across DMs; avoid partial/duplicate replies when reasoning is enabled. (#655) — thanks @antons.
- Models/Auth: allow MiniMax API configs without `models.providers.minimax.apiKey` (auth profiles / `MINIMAX_API_KEY`). (#656) — thanks @mneves75.
- Agents: avoid duplicate replies when the message tool sends. (#659) — thanks @mickahouan.
+- Agents: harden Cloud Code Assist tool ID sanitization (toolUse/toolCall/toolResult) and scrub extra JSON Schema constraints. (#665) — thanks @sebslight.
- Agents/Tools: resolve workspace-relative Read/Write/Edit paths; align bash default cwd. (#642) — thanks @mukhtharcm.
- Tests/Agents: add regression coverage for workspace tool path resolution and bash cwd defaults.
- iOS/Android: enable stricter concurrency/lint checks; fix Swift 6 strict concurrency issues + Android lint errors (ExifInterface, obsolete SDK check). (#662) — thanks @KristijanJovanovski.
diff --git a/README.md b/README.md
index e1bc9c20a..e9ef6423d 100644
--- a/README.md
+++ b/README.md
@@ -458,16 +458,18 @@ Thanks to all clawtributors:
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
diff --git a/scripts/clawtributors-map.json b/scripts/clawtributors-map.json
index 5d75a5e8a..5327ebbff 100644
--- a/scripts/clawtributors-map.json
+++ b/scripts/clawtributors-map.json
@@ -19,6 +19,7 @@
},
"emailToLogin": {
"steipete@gmail.com": "steipete",
+ "sbarrios93@gmail.com": "sebslight",
"rltorres26+github@gmail.com": "RandyVentures",
"hixvac@gmail.com": "VACInc"
}
diff --git a/src/agents/pi-embedded-helpers.test.ts b/src/agents/pi-embedded-helpers.test.ts
index a9f63fc9f..ec7b0a662 100644
--- a/src/agents/pi-embedded-helpers.test.ts
+++ b/src/agents/pi-embedded-helpers.test.ts
@@ -363,6 +363,37 @@ describe("sanitizeSessionMessagesImages", () => {
expect((content as Array<{ type?: string }>)[0]?.type).toBe("toolCall");
});
+ it("sanitizes tool ids for assistant blocks and tool results", async () => {
+ const input = [
+ {
+ role: "assistant",
+ content: [
+ { type: "toolUse", id: "call_abc|item:123", name: "test", input: {} },
+ {
+ type: "toolCall",
+ id: "call_abc|item:456",
+ name: "bash",
+ arguments: {},
+ },
+ ],
+ },
+ {
+ role: "toolResult",
+ toolUseId: "call_abc|item:123",
+ content: [{ type: "text", text: "ok" }],
+ },
+ ] satisfies AgentMessage[];
+
+ const out = await sanitizeSessionMessagesImages(input, "test");
+
+ const assistant = out[0] as { content?: Array<{ id?: string }> };
+ expect(assistant.content?.[0]?.id).toBe("call_abc_item_123");
+ expect(assistant.content?.[1]?.id).toBe("call_abc_item_456");
+
+ const toolResult = out[1] as { toolUseId?: string };
+ expect(toolResult.toolUseId).toBe("call_abc_item_123");
+ });
+
it("filters whitespace-only assistant text blocks", async () => {
const input = [
{
diff --git a/src/agents/pi-embedded-helpers.ts b/src/agents/pi-embedded-helpers.ts
index 049647ac5..61f0b5798 100644
--- a/src/agents/pi-embedded-helpers.ts
+++ b/src/agents/pi-embedded-helpers.ts
@@ -106,12 +106,20 @@ export async function sanitizeSessionMessagesImages(
const sanitizedToolCallId = toolMsg.toolCallId
? sanitizeToolCallId(toolMsg.toolCallId)
: undefined;
+ const toolUseId = (toolMsg as { toolUseId?: unknown }).toolUseId;
+ const sanitizedToolUseId =
+ typeof toolUseId === "string" && toolUseId
+ ? sanitizeToolCallId(toolUseId)
+ : undefined;
const sanitizedMsg = {
...toolMsg,
content: nextContent,
...(sanitizedToolCallId && {
toolCallId: sanitizedToolCallId,
}),
+ ...(sanitizedToolUseId && {
+ toolUseId: sanitizedToolUseId,
+ }),
};
out.push(sanitizedMsg);
continue;
@@ -153,9 +161,13 @@ export async function sanitizeSessionMessagesImages(
if (typeof id !== "string" || !id) return block;
// Cloud Code Assist tool blocks require ids matching ^[a-zA-Z0-9_-]+$.
- if (type === "functionCall" || type === "toolUse" || type === "toolCall") {
+ if (
+ type === "functionCall" ||
+ type === "toolUse" ||
+ type === "toolCall"
+ ) {
return {
- ...((block as unknown) as Record),
+ ...(block as unknown as Record),
id: sanitizeToolCallId(id),
};
}
diff --git a/src/agents/pi-tools.test.ts b/src/agents/pi-tools.test.ts
index a4eabc81f..d89e2051b 100644
--- a/src/agents/pi-tools.test.ts
+++ b/src/agents/pi-tools.test.ts
@@ -385,6 +385,18 @@ describe("createClawdbotCodingTools", () => {
"$defs",
"definitions",
"examples",
+ "minLength",
+ "maxLength",
+ "minimum",
+ "maximum",
+ "multipleOf",
+ "pattern",
+ "format",
+ "minItems",
+ "maxItems",
+ "uniqueItems",
+ "minProperties",
+ "maxProperties",
]);
const findUnsupportedKeywords = (
@@ -399,9 +411,24 @@ describe("createClawdbotCodingTools", () => {
});
return found;
}
- for (const [key, value] of Object.entries(
- schema as Record,
- )) {
+
+ const record = schema as Record;
+ const properties =
+ record.properties &&
+ typeof record.properties === "object" &&
+ !Array.isArray(record.properties)
+ ? (record.properties as Record)
+ : undefined;
+ if (properties) {
+ for (const [key, value] of Object.entries(properties)) {
+ found.push(
+ ...findUnsupportedKeywords(value, `${path}.properties.${key}`),
+ );
+ }
+ }
+
+ for (const [key, value] of Object.entries(record)) {
+ if (key === "properties") continue;
if (unsupportedKeywords.has(key)) {
found.push(`${path}.${key}`);
}