Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions actions/setup/js/safe_output_type_validator.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,15 @@ function validateField(value, fieldName, validation, itemType, lineNum, options)
}

if (validation.type === "array") {
// Backward compatibility: create_issue agents sometimes provide comma-separated labels as a string.
// Normalize this into a string array before strict array validation.
if (itemType === "create_issue" && fieldName === "labels" && typeof value === "string") {
value = value
.split(",")
.map(item => item.trim())
.filter(Boolean);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty-string input silently produces an empty array and passes required-field validation: labels: ""split(",")[""]filter(Boolean)[]. Array.isArray([]) is true, so add_labels.labels (which is required: true) will pass validation with an empty labels list.

💡 Impact and fix

An add_labels call with labels: "" passes the JS validator but then silently does nothing at the GitHub API level (zero labels to add). The operator sees no error but no label is applied.

The fix is to reject normalised-empty results when the field is required:

if (fieldName === "labels" && typeof value === "string") {
  value = value.split(",").map(item => item.trim()).filter(Boolean);
  // Guard: empty string must not bypass required validation
  if (value.length === 0 && validation.required) {
    return {
      isValid: false,
      error: `Line ${lineNum}: ${itemType} requires a non-empty '${fieldName}' field`,
    };
  }
}

Alternatively, add a minItems: 1 check to the general array validation path for any required array field.

}

if (!Array.isArray(value)) {
// For required fields, use "requires a" format for both missing and wrong type
if (validation.required) {
Expand Down
9 changes: 9 additions & 0 deletions actions/setup/js/safe_output_type_validator.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,15 @@ describe("safe_output_type_validator", () => {
expect(Array.isArray(result.normalizedItem.labels)).toBe(true);
});

it("should normalize comma-separated labels string to array", async () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test coverage gap: only create_issue with a two-element comma string is tested, leaving undocumented (and unverified) the behaviour for the other two tools that share the same code path.

💡 Suggested additions

Add at least these cases:

// Verify the normalization is intentional for add_labels
it("should normalize comma-separated labels string for add_labels", async () => {
  const { validateItem } = await import("./safe_output_type_validator.cjs");
  const result = validateItem({ type: "add_labels", labels: "bug, enhancement" }, "add_labels", 1);
  expect(result.isValid).toBe(true);
  expect(result.normalizedItem.labels).toEqual(["bug", "enhancement"]);
});

// Guard against empty-string silent pass for a required array field
it("should reject empty string for required labels field", async () => {
  const { validateItem } = await import("./safe_output_type_validator.cjs");
  const result = validateItem({ type: "add_labels", labels: "" }, "add_labels", 1);
  expect(result.isValid).toBe(false); // fails today — empty array passes required check
});

The second test will fail with the current code, surfacing the empty-string issue noted on the normalization block.

const { validateItem } = await import("./safe_output_type_validator.cjs");

const result = validateItem({ type: "create_issue", title: "Test", body: "Detailed issue body text.", labels: "reliability, telemetry" }, "create_issue", 1);

expect(result.isValid).toBe(true);
expect(result.normalizedItem.labels).toEqual(["reliability", "telemetry"]);
});

it("should reject array with non-string items", async () => {
const { validateItem } = await import("./safe_output_type_validator.cjs");

Expand Down
6 changes: 3 additions & 3 deletions actions/setup/js/safe_outputs_tools.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[
{
"name": "create_issue",
"description": "WRITE-ONCE: do NOT call this tool with empty or placeholder arguments to probe or discover its schema \u2014 required fields (title, body) are listed in this schema; if you are not ready to open the real issue, call `noop` instead. Creates a new GitHub issue for tracking bugs, feature requests, or tasks. Use this for actionable work items that need assignment, labeling, and status tracking. For reports, announcements, or status updates that don't require task tracking, use create_discussion instead.",
"description": "WRITE-ONCE: do NOT call this tool with empty or placeholder arguments to probe or discover its schema \u2014 required fields (title, body) are listed in this schema; if you are not ready to open the real issue, call `noop` instead. Creates a new GitHub issue for tracking bugs, feature requests, or tasks. Use this for actionable work items that need assignment, labeling, and status tracking. For reports, announcements, or status updates that don't require task tracking, use create_discussion instead. Compatibility: labels may be passed as either an array of strings or a comma-separated string; string input is split, trimmed, and normalized to an array.",
"inputSchema": {
"type": "object",
"required": ["title", "body"],
Expand All @@ -16,11 +16,11 @@
"description": "Detailed issue description in Markdown. Must be the final intended body \u2014 not a placeholder or test value. Do NOT repeat the title as a heading since it already appears as the issue's h1. Include context, reproduction steps, or acceptance criteria as appropriate."
},
"labels": {
"type": "array",
"type": ["array", "string"],
"items": {
"type": "string"
},
"description": "Labels to categorize the issue (e.g., 'bug', 'enhancement'). Labels must exist in the repository."
"description": "Labels to categorize the issue (e.g., 'bug', 'enhancement'). Labels must exist in the repository. Accepts either an array of strings or a comma-separated string; string input is split on commas, trimmed, and normalized to an array."
},
"fields": {
"type": "array",
Expand Down
6 changes: 3 additions & 3 deletions docs/src/content/docs/specs/safe-outputs-specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -875,7 +875,7 @@ Compiler generates tool schemas:
"properties": {
"title": {"type": "string"},
"body": {"type": "string"},
"labels": {"type": "array", "items": {"type": "string"}}
"labels": {"type": ["array", "string"], "items": {"type": "string"}}
}
}
}
Expand Down Expand Up @@ -1993,14 +1993,14 @@ Schema-only updates without matching agent/runtime sync updates **MUST NOT** be
```json
{
"name": "create_issue",
"description": "Create a new GitHub issue for tracking bugs, feature requests, or tasks.",
"description": "Create a new GitHub issue for tracking bugs, feature requests, or tasks. Compatibility: labels may be passed as an array or comma-separated string.",
"inputSchema": {
"type": "object",
"required": ["title", "body"],
"properties": {
"title": {"type": "string", "description": "Issue title"},
"body": {"type": "string", "description": "Issue description in Markdown"},
"labels": {"type": "array", "items": {"type": "string"}},
"labels": {"type": ["array", "string"], "items": {"type": "string"}},
"fields": {
"type": "array",
"items": {
Expand Down
9 changes: 6 additions & 3 deletions pkg/workflow/js/safe_outputs_tools.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[
{
"name": "create_issue",
"description": "WRITE-ONCE: do NOT call this tool with empty or placeholder arguments to probe or discover its schema \u2014 required fields (title, body) are listed in this schema; if you are not ready to open the real issue, call `noop` instead. Creates a new GitHub issue for tracking bugs, feature requests, or tasks. Use this for actionable work items that need assignment, labeling, and status tracking. For reports, announcements, or status updates that don't require task tracking, use create_discussion instead.",
"description": "WRITE-ONCE: do NOT call this tool with empty or placeholder arguments to probe or discover its schema \u2014 required fields (title, body) are listed in this schema; if you are not ready to open the real issue, call `noop` instead. Creates a new GitHub issue for tracking bugs, feature requests, or tasks. Use this for actionable work items that need assignment, labeling, and status tracking. For reports, announcements, or status updates that don't require task tracking, use create_discussion instead. Compatibility: labels may be passed as either an array of strings or a comma-separated string; string input is split, trimmed, and normalized to an array.",
"inputSchema": {
"type": "object",
"required": [
Expand All @@ -19,11 +19,14 @@
"description": "Detailed issue description in Markdown. Must be the final intended body \u2014 not a placeholder or test value. Do NOT repeat the title as a heading since it already appears as the issue's h1. Include context, reproduction steps, or acceptance criteria as appropriate."
},
"labels": {
"type": "array",
"type": [
"array",
"string"
],
"items": {
"type": "string"
},
"description": "Labels to categorize the issue (e.g., 'bug', 'enhancement'). Labels must exist in the repository."
"description": "Labels to categorize the issue (e.g., 'bug', 'enhancement'). Labels must exist in the repository. Accepts either an array of strings or a comma-separated string; string input is split on commas, trimmed, and normalized to an array."
},
"fields": {
"type": "array",
Expand Down
4 changes: 2 additions & 2 deletions schemas/agent-output.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@
"minLength": 1
},
"labels": {
"type": "array",
"description": "Optional labels to add to the issue",
"type": ["array", "string"],
"description": "Optional labels to add to the issue. Accepts an array of strings or a comma-separated string (e.g. \"bug, reliability\") that is normalized into an array.",
"items": {
"type": "string"
Comment on lines 79 to 83
}
Expand Down
Loading