Skip to content
Closed
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
2 changes: 1 addition & 1 deletion .github/workflows/smoke-codex.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 8 additions & 4 deletions .github/workflows/smoke-codex.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,16 @@ features:
10. **Set Issue Field Testing**:
- After creating the smoke-test issue, use `set_issue_field` exactly once on that new issue
- Reference the created issue using the `temporary_id` declared on the `create_issue` output: set `issue_number: '#aw_smoke_issue'` in the `set_issue_field` message
- Discover available issue fields and choose one compatible field/value pair:
- **Before choosing a field**, run this bash command to discover available fields (do NOT guess field names):
```
OWNER="${GITHUB_REPOSITORY%%/*}" && REPO="${GITHUB_REPOSITORY##*/}" && gh api graphql -f query='query($owner:String!,$repo:String!){repository(owner:$owner,name:$repo){issueFields(first:20){nodes{__typename ...on IssueFieldText{id name}...on IssueFieldNumber{id name}...on IssueFieldDate{id name}...on IssueFieldSingleSelect{id name options{id name}}}}}}' -f owner="$OWNER" -f repo="$REPO"
```
- From the returned list, choose ONE field/value pair:
- date field → today's date in `YYYY-MM-DD` format (prefer this type)
- text field → short text value
- number field → numeric value
- date field → `YYYY-MM-DD`
- single-select field → an existing option name
- If no editable issue fields are available, report this test as skipped with reason
- single-select field → an existing option name exactly as returned
- If the discovery command fails or returns no fields, report this test as skipped with reason

## Output

Expand Down
14 changes: 11 additions & 3 deletions actions/setup/js/safe_output_handler_manager.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -667,15 +667,23 @@ function isFailedProcessingResult(result) {
return Boolean(result?.success === false && !result?.deferred && !result?.skipped && !result?.cancelled);
}

/** Types whose failures are surfaced as warnings rather than failing the safe_outputs job. */
const REPORT_ONLY_FAILURE_TYPES = new Set(["assign_to_agent", "upload_artifact"]);
/**
* Types whose failures are surfaced as warnings rather than failing the safe_outputs job.
* - assign_to_agent: agent assignment can fail after other safe outputs already succeeded.
* - upload_artifact: artifact uploads are best-effort and non-critical.
* - set_issue_field: project-board fields can drift (renamed/removed); a bad field write
* should not sink an otherwise-successful safe-outputs batch.
*/
const REPORT_ONLY_FAILURE_TYPES = new Set(["assign_to_agent", "upload_artifact", "set_issue_field"]);
Comment thread
github-actions[bot] marked this conversation as resolved.

/**
* Determine whether a failed result should be reported without failing the safe_outputs job.
* Agent assignment can fail after other safe outputs already succeeded, so those failures
* are surfaced through dedicated outputs and summaries instead of failing the entire job.
* Artifact uploads are best-effort and non-critical: a failed upload should not fail an
* otherwise-successful run.
* Issue field writes target project-board fields that can drift over time; a single bad
* field write should not sink an otherwise-successful safe-outputs batch.
*
* @param {{type?: string, success?: boolean, deferred?: boolean, skipped?: boolean, cancelled?: boolean}|null|undefined} result
* @returns {boolean}
Expand Down Expand Up @@ -1608,7 +1616,7 @@ async function main() {
core.info(`Successful: ${successCount}`);
core.info(`Failed: ${failureCount}`);
if (reportOnlyFailureCount > 0) {
core.info(`Reported assignment failures: ${reportOnlyFailureCount}`);
core.info(`Non-fatal failures (reported only): ${reportOnlyFailureCount}`);
}
if (cancelledCount > 0) {
core.info(`Cancelled (code push failed): ${cancelledCount}`);
Expand Down
37 changes: 37 additions & 0 deletions actions/setup/js/safe_output_handler_manager.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,43 @@ describe("Safe Output Handler Manager", () => {
expect(reportOnlyFailures).toEqual([{ type: "upload_artifact", success: false, error: "artifact twirp CreateArtifact failed (400)" }]);
expect(fatalFailures).toEqual([{ type: "create_issue", success: false, error: "Validation failed" }]);
});

it("treats failed set_issue_field results as report-only", () => {
expect(
isReportOnlyFailureResult({
type: "set_issue_field",
success: false,
})
).toBe(true);
});

it("does not treat skipped or cancelled set_issue_field results as report-only", () => {
expect(
isReportOnlyFailureResult({
type: "set_issue_field",
success: false,
skipped: true,
})
).toBe(false);
expect(
isReportOnlyFailureResult({
type: "set_issue_field",
success: false,
cancelled: true,
})
).toBe(false);
});

it("partitions set_issue_field failures as report-only, not fatal", () => {
const { fatalFailures, reportOnlyFailures } = partitionFailureResults([
{ type: "set_issue_field", success: false, error: 'Issue field "Status" not found.' },
{ type: "create_issue", success: false, error: "Validation failed" },
{ type: "create_discussion", success: true },
]);

expect(reportOnlyFailures).toEqual([{ type: "set_issue_field", success: false, error: 'Issue field "Status" not found.' }]);
expect(fatalFailures).toEqual([{ type: "create_issue", success: false, error: "Validation failed" }]);
});
});

describe("loadHandlers", () => {
Expand Down