Skip to content

fix: soft-skip target:triggering safe outputs when no triggering context#40035

Merged
pelikhan merged 7 commits into
mainfrom
copilot/aw-failures-fix-schedule-workflow
Jun 18, 2026
Merged

fix: soft-skip target:triggering safe outputs when no triggering context#40035
pelikhan merged 7 commits into
mainfrom
copilot/aw-failures-fix-schedule-workflow

Conversation

Copilot AI commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Scheduled workflows (no triggering issue or PR) were failing the entire Process Safe Outputs step when an agent emitted update_issue with target: triggering — the default. The skip was tallied as a fatal failure rather than a no-op.

MCP-level validation (primary fix)

Added context-aware handlers in safe_outputs_handlers.cjs that validate event context before writing to the NDJSON safe-outputs file. When target is triggering (explicit or by default) and no appropriate context exists, the handler returns an inline error to the agent so it can adjust its output immediately rather than failing downstream.

This validation is applied to all safe output tools that support target: triggering:

  • updateIssueHandler — rejects when not in an issue context; suggests create_discussion, create_issue, or target: '*' with an explicit issue_number.
  • updatePullRequestHandler — rejects when not in a PR context; suggests create_discussion, create_issue, or target: '*' with an explicit pull_request_number.
  • addCommentHandler — rejects when not in an issue, PR, or discussion context and no explicit item_number is provided. Explicit item_number/issue_number/pr-number in the args bypasses the check, mirroring downstream add_comment.cjs behaviour.

All handlers correctly pass through:

  • Explicit non-triggering targets (e.g. target: '*')
  • workflow_dispatch runs with aw_context resolving to an appropriate event

Registered the handlers in safe_outputs_tools_loader.cjs.

Also fixed a missing entry: invocation_context_helpers.cjs is now listed in SAFE_OUTPUTS_FILES in setup.sh as required by its transitive dependency on safe_outputs_handlers.cjs.

Belt-and-suspenders (existing NDJSON entries)

Patched update_handler_factory.cjs to propagate shouldFail: false from resolveTarget() through createStandardResolveNumber, and return { success: false, skipped: true } in handleUpdate. This covers existing NDJSON entries already written without MCP validation and any codepaths that bypass the MCP server — isFailedProcessingResult() already exempts skipped: true results from the failure count.

// Before: resolveTarget returned shouldFail:false but createStandardResolveNumber discarded it
// → handleUpdate returned { success: false } → counted as fatal failure

// After: shouldFail:false propagates → handleUpdate returns { success: false, skipped: true }
// → isFailedProcessingResult() returns false → step stays green

Copilot AI and others added 2 commits June 18, 2026 11:40
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
…ft skip

- Add MCP-level validation in updateIssueHandler to reject update_issue
  when target is triggering (or unset) and there's no issue context
- Belt-and-suspenders: patch update_handler_factory to propagate
  shouldFail:false from resolveTarget as skipped:true result, preventing
  any existing NDJSON entries from failing the Process Safe Outputs step
- Add tests for both MCP handler validation and factory soft-skip logic

Fixes #40017

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix scheduled workflow failing on process safe outputs fix: soft-skip update_issue target:triggering when no issue context Jun 18, 2026
Copilot AI requested a review from pelikhan June 18, 2026 11:49
@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot review other safe outputs that support target triggering to have the same validation behavior in the MCP server

Copilot AI and others added 2 commits June 18, 2026 12:22
…nt and update_pull_request

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

@copilot review other safe outputs that support target triggering to have the same validation behavior in the MCP server

Done. Applied the same MCP-level context validation to the other two handlers that support target: triggering:

updatePullRequestHandler — rejects when target: triggering (default) and the workflow is not running in a PR context (pull_request, pull_request_target, pull_request_review, pull_request_review_comment, or issue_comment on a PR). Returns an actionable error pointing to create_discussion/create_issue or using target: '*' with an explicit pull_request_number.

addCommentHandler — rejects when target: triggering (default), no explicit item_number/issue_number/pr-number in the args, and the workflow has no issue/PR/discussion context. The explicit-item-number bypass mirrors the downstream add_comment.cjs behaviour where an explicit number is resolved before falling back to the triggering context.

Also fixed a pre-existing omission: invocation_context_helpers.cjs (added as a require in the previous commit) was missing from SAFE_OUTPUTS_FILES in setup.sh, which was caught by setup_sh_file_lists.test.cjs.

Commit: fix: add MCP-level target:triggering context validation for add_comment and update_pull_request

Copilot AI changed the title fix: soft-skip update_issue target:triggering when no issue context fix: soft-skip target:triggering safe outputs when no triggering context Jun 18, 2026
@pelikhan pelikhan marked this pull request as ready for review June 18, 2026 12:38
Copilot AI review requested due to automatic review settings June 18, 2026 12:38

Copilot AI left a comment

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.

Pull request overview

This PR fixes a failure mode in the “Process Safe Outputs” pipeline where target: triggering safe-output updates emitted during scheduled (non-issue/PR) runs were counted as fatal failures instead of being treated as no-ops/skips. It does so by adding context-aware validation at tool-invocation time (MCP handlers) and by propagating shouldFail:false through the NDJSON-processing update handler factory as a “soft skip” (skipped: true).

Changes:

  • Added MCP-level context validation for add_comment, update_issue, and update_pull_request so target: triggering is rejected early when no triggering context exists, preventing NDJSON writes that would fail downstream.
  • Updated the NDJSON update handler factory to propagate shouldFail:false and return { success: false, skipped: true } so scheduled-run “triggering” updates don’t count toward fatal failures.
  • Updated setup/runtime packaging and compiled workflow locks (including tool allowlists) to include newly required helpers and permissions.
Show a summary per file
File Description
actions/setup/setup.sh Ensures invocation_context_helpers.cjs is packaged as part of safe-outputs runtime files.
actions/setup/js/update_handler_factory.cjs Propagates shouldFail:false and converts those cases into soft-skipped processing results.
actions/setup/js/update_handler_factory.test.cjs Adds coverage ensuring shouldFail:false is preserved and produces skipped: true results.
actions/setup/js/safe_outputs_handlers.cjs Adds context-aware early validation for add_comment, update_issue, and update_pull_request before NDJSON writes.
actions/setup/js/safe_outputs_handlers.test.cjs Extends handler tests to cover schedule/push/non-triggering contexts and workflow_dispatch aw_context resolution.
actions/setup/js/safe_outputs_tools_loader.cjs Registers the new update_issue handler wiring into the safe-outputs tool loader.
actions/setup-cli/install.sh Updates sync note comment to reflect the canonical script source.
.github/workflows/spec-librarian.lock.yml Updates compiled workflow lock to include shell(awk) in the allowed tools list.
.github/workflows/go-fan.lock.yml Updates compiled workflow lock to include Bash(awk) in the allowed tools list.
.github/workflows/daily-testify-uber-super-expert.lock.yml Updates compiled workflow lock to include shell(awk) in the allowed tools list.
.github/workflows/daily-file-diet.lock.yml Updates compiled workflow lock to include shell(awk) in the allowed tools list.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 11/11 changed files
  • Comments generated: 0

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR #40035 does not have the 'implementation' label and has 0 new lines of code in business logic directories (≤100 threshold). requires_adr_by_default_volume is false.

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions github-actions Bot mentioned this pull request Jun 18, 2026

@github-actions github-actions Bot left a comment

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.

REQUEST_CHANGES — one blocking issue; two non-blocking observations

The belt-and-suspenders approach (MCP-level early rejection + NDJSON-layer soft-skip) is the right design. The shouldFail:false propagation through createStandardResolveNumber is clean and the new handler tests have solid coverage. The blocking issue is a correctness hole in the exception-handling path that defeats the stated MCE1 goal.

Findings summary

Blocking (1)

  • All three new try/catch blocks around resolveInvocationContext silently swallow ERR_VALIDATION from checkAllowedRepo — a real security-validation error gets discarded instead of surfaced to the agent (see inline comment on line 1912).

Non-blocking (2)

  • prEventNames Set literal is duplicated verbatim in addCommentHandler and updatePullRequestHandler; extract a module-level constant.
  • One bypass test relies on the implicit default event context rather than setting it explicitly.

🔎 Code quality review by PR Code Quality Reviewer

invocationContext = resolveInvocationContext(context);
} catch {
// If context resolution fails fall through and let the downstream handler deal with it.
return defaultHandler("update_issue")(args || {});

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.

All three handlers swallow ERR_VALIDATION from checkAllowedRepo, silently defeating MCE1 early feedback when a workflow_dispatch specifies a disallowed cross-repo target.

💡 Details and suggested fix

resolveInvocationContext throws new Error(ERR_VALIDATION + ": ...") via checkAllowedRepo when target_repo is not in the allowlist (SEC-005). All three catch blocks swallow this without telling the agent:

  • updateIssueHandler (this catch, line 1912): calls defaultHandler, writes to NDJSON, returns success to agent
  • updatePullRequestHandler (line ~1959): same pattern
  • addCommentHandler (line ~1654): leaves invocationContext undefined → validation skipped → proceeds to write NDJSON

The agent gets result: "success" from the MCP layer, then hits an unexpected failure during NDJSON processing. The "immediate feedback before writing to NDJSON" goal is broken on exactly the adversarial/misconfigured path.

Fix — distinguish the error kind before deciding how to handle it:

} catch (err) {
  // A validation error (e.g. disallowed target_repo / SEC-005) is a real failure,
  // not an unresolvable-context situation. Surface it immediately.
  if (err?.message?.startsWith(ERR_VALIDATION)) {
    return buildIntentErrorResponse(err.message);
  }
  // Unexpected structural error — fall through to downstream handler.
  return defaultHandler("update_issue")(args || {});
}

Same change needed in updatePullRequestHandler and addCommentHandler (where the catch should re-throw or return an error rather than silently continuing).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 132323f. All three catch blocks now inspect err?.message?.startsWith(ERR_VALIDATION) before deciding how to handle: validation errors (e.g. checkAllowedRepo / SEC-005) are returned immediately via buildIntentErrorResponse; unexpected structural errors fall through to the downstream handler as before.

if (invocationContext !== undefined) {
const effectiveEventName = invocationContext?.eventName || context.eventName;
const effectivePayload = invocationContext?.eventPayload || context.payload;
const prEventNames = new Set(["pull_request", "pull_request_target", "pull_request_review", "pull_request_review_comment"]);

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.

prEventNames Set is copy-pasted identically at line 1659 (addCommentHandler) and line 1963 (updatePullRequestHandler); adding a new PR event type will silently miss one.

💡 Suggested fix

Extract a module-level constant to eliminate the duplication:

// Near the top of the module, after requires:
const PR_EVENT_NAMES = new Set([
  "pull_request",
  "pull_request_target",
  "pull_request_review",
  "pull_request_review_comment",
]);

Then both spots just reference PR_EVENT_NAMES. If merge_group or another PR-adjacent event ever needs coverage, there is one place to update.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 132323f. Extracted PR_EVENT_NAMES as a module-level const (new Set([...]) defined once at load time). Both addCommentHandler and updatePullRequestHandler now reference the shared constant, so a future addition only needs one edit.

}
});

it("should write entry when explicit item_number bypasses context check in non-issue/PR event", () => {

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.

This test asserts bypass behavior without explicitly setting a non-triggering context; if the default test context ever changes, it will pass for the wrong reason.

💡 Suggested fix

The test relies on global.context having eventName: "push" from the module-level mockContext setup (line 35). That's currently true, but the intent is invisible in the test body. Any future beforeEach that sets an issue or PR context will make the test vacuously pass even if the bypass logic is broken.

Make the context explicit, consistent with every other new test in this block:

it("should write entry when explicit item_number bypasses context check in non-issue/PR event", () => {
  const savedContext = global.context;
  global.context = { ...global.context, eventName: "push", payload: {} };
  try {
    const result = handlers.addCommentHandler({
      body: "A real comment body that is substantive enough",
      item_number: 42,
    });
    expect(result.isError).toBeUndefined();
    const responseData = JSON.parse(result.content[0].text);
    expect(responseData.result).toBe("success");
    expect(mockAppendSafeOutput).toHaveBeenCalledWith(
      expect.objectContaining({ type: "add_comment", item_number: 42 })
    );
  } finally {
    global.context = savedContext;
  }
});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 132323f. The test now explicitly sets global.context = { ...global.context, eventName: "push", payload: {} } before calling the handler, making the non-triggering context visible in the test body and immune to future beforeEach changes.

@github-actions github-actions Bot left a comment

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.

Skills-Based Review 🧠

Applied /diagnose, /tdd, and /zoom-out to this bug-fix PR. All findings are non-blocking suggestions — no request for changes.

📋 Key Themes & Highlights

Key Themes

  • Consistency (3 comments): The three new handlers share the same pattern but implement it with slight variations — exception handling style, a repeated two-line extraction, and a new Set() allocated on every call. Worth unifying before this grows to more handlers.
  • Test coverage gaps (2 comments): The issue_comment-on-PR boundary (rejected by update_issue, accepted by add_comment) and bare workflow_dispatch without an event_name input are untested edge cases that could regress silently.
  • shouldFail naming (1 comment): The flag propagates correctly but the negative-sense name makes the callsite harder to scan.

Positive Highlights

  • ✅ Two-layer fix is well architected: MCP-level early rejection gives the LLM an actionable error before touching NDJSON; downstream skipped: true protects against pre-existing entries.
  • ✅ Error messages are excellent — they name the event, explain why it doesn't work, and suggest concrete alternatives (create_discussion, create_issue, explicit item_number).
  • ✅ Catching the missing invocation_context_helpers.cjs entry in setup.sh is the kind of latent-crash fix that's easy to overlook — good catch.
  • ✅ Registering updateIssueHandler in safe_outputs_tools_loader.cjs was the key prerequisite for the primary fix; correctly identified and wired.
  • ✅ The regression test expansion (361 additions) across all three handlers and multiple event types gives strong confidence in the new behaviour.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer

} catch {
// Context resolution failed; skip validation and let downstream handle gracefully
}
if (invocationContext !== undefined) {

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.

[/diagnose] The invocationContext !== undefined guard is an unconventional way to detect a thrown exception — it relies on the catch block leaving the let invocationContext binding at its undefined initial value. updateIssueHandler and updatePullRequestHandler (added in this same PR) use an explicit early-return inside catch, which is easier to reason about.

💡 Suggested approach for consistency

Match the pattern used in the sibling handlers:

let invocationContext;
try {
  invocationContext = resolveInvocationContext(context);
} catch {
  // Context resolution failed; let downstream handle gracefully.
  invocationContext = null;
}
if (invocationContext != null) {
  // validate ...
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 132323f. addCommentHandler now initialises let invocationContext = null, sets it to null explicitly in the ERR_VALIDATION path (which returns early), and guards on invocationContext != null — matching the early-return pattern used in updateIssueHandler and updatePullRequestHandler.

if (invocationContext !== undefined) {
const effectiveEventName = invocationContext?.eventName || context.eventName;
const effectivePayload = invocationContext?.eventPayload || context.payload;
const prEventNames = new Set(["pull_request", "pull_request_target", "pull_request_review", "pull_request_review_comment"]);

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.

[/diagnose] new Set([...]) is instantiated on every addCommentHandler call. The same set appears again at line 1963 inside updatePullRequestHandler. Extract it as a module-level constant to avoid repeated allocations on every invocation.

💡 Suggested fix

At the top of the module, before any handler definitions:

const PR_EVENT_NAMES = new Set([
  'pull_request',
  'pull_request_target',
  'pull_request_review',
  'pull_request_review_comment',
]);

Then reference PR_EVENT_NAMES in both handlers.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 132323f. PR_EVENT_NAMES is now a module-level const Set instantiated once at load time, referenced by both addCommentHandler and updatePullRequestHandler.

// Context resolution failed; skip validation and let downstream handle gracefully
}
if (invocationContext !== undefined) {
const effectiveEventName = invocationContext?.eventName || context.eventName;

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.

[/zoom-out] The two-line context extraction pattern (effectiveEventName + effectivePayload) is repeated verbatim in all three handlers (lines 1657–1658, 1914–1915, 1961–1962). Extracting a small helper would remove the duplication and make a single place to fix if resolveInvocationContext's return shape ever changes.

💡 Suggested helper
function resolveEffectiveContext(invocationContext, rawContext) {
  return {
    eventName: invocationContext?.eventName || rawContext.eventName,
    payload:   invocationContext?.eventPayload || rawContext.payload,
  };
}

Each handler then becomes one line: const { eventName: effectiveEventName, payload: effectivePayload } = resolveEffectiveContext(invocationContext, context);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 132323f. Added resolveEffectiveContext(invocationContext, rawContext) as a module-level helper that returns { effectiveEventName, effectivePayload }. All three handlers now call it with one line instead of repeating the two-line extraction.

// shouldFail:false means the target cannot be resolved in this context but it is not
// an error (e.g. target:triggering on a schedule run has no triggering issue).
// Treat it as a soft skip so it does not count toward fatal failures.
if (itemNumberResult.shouldFail === false) {

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.

[/diagnose] shouldFail === false is correct (strict equality guards against undefined), but the boolean flag name requires double-negation thinking: "should fail" set to false means "is a soft skip". A positive-sense name like skipSilently: true or isContextMismatch: true would make the callsites read as intent rather than as a negated condition.

💡 Example rename

In resolveTarget return sites: { success: false, skipSilently: true, error: ... }

In createStandardResolveNumber: return { success: false, skipSilently: targetResult.skipSilently, error: ... };

In createUpdateHandlerFactory: if (itemNumberResult.skipSilently) { ... }

This reads: "if we should skip silently, do so" rather than "if we should NOT fail, skip".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Declining this rename. shouldFail is used in ~10 files outside this PR (safe_output_helpers.cjs, add_comment.cjs, comment_memory.cjs, assign_to_agent.cjs, create_check_run.cjs, submit_pr_review.cjs, safe_output_processor.cjs). Renaming a public contract field across all callsites goes well beyond the scope of this fix and risks introducing regressions in production handlers. The existing shouldFail === false check is correct and the comment in the factory already explains its semantics. Happy to track the rename as a follow-up task.

});
});

describe("updateIssueHandler", () => {

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.

[/tdd] The updateIssueHandler describe block is missing a boundary test for issue_comment events that fire on a PR. The code at line 1916 in handlers intentionally treats issue_comment with payload.issue.pull_request set as PR context, not issue context, so update_issue should reject it. Without a test this invariant is invisible and could silently regress.

💡 Suggested test
it('should return intent error for issue_comment on a PR (not issue context)', () => {
  const savedContext = global.context;
  global.context = {
    ...global.context,
    eventName: 'issue_comment',
    payload: { issue: { number: 7, pull_request: { url: '(redacted)' } } },
  };
  try {
    const result = handlers.updateIssueHandler({ body: 'Update body' });
    expect(result.isError).toBe(true);
    const data = JSON.parse(result.content[0].text);
    expect(data.error).toContain('issue context');
    expect(mockAppendSafeOutput).not.toHaveBeenCalled();
  } finally {
    global.context = savedContext;
  }
});

A companion test for addCommentHandler should confirm the inverse: issue_comment on a PR succeeds (it is valid PR context for add_comment).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 132323f. Added two tests:

  • updateIssueHandler: issue_comment on a PR → isError: true, error contains "issue context", appendSafeOutput not called.
  • addCommentHandler + updatePullRequestHandler: issue_comment on a PR → success, entry written (valid PR context for both).

expect(mockAppendSafeOutput).toHaveBeenCalledWith(expect.objectContaining({ type: "add_comment", item_number: 42 }));
});

it("should write entry on workflow_dispatch with issue aw_context", () => {

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.

[/tdd] Coverage exists for workflow_dispatch with aw_context resolving to an issue/PR, but there is no test for a bare workflow_dispatch with no event_name in inputs. In that case resolveInvocationContext returns eventName: "workflow_dispatch", which is not issue/PR/discussion context, so all three handlers should reject it. Without a test, a future change to resolveInvocationContext that accidentally passes "workflow_dispatch" through could go undetected.

💡 Suggested test (addCommentHandler; repeat for update_issue and update_pull_request)
it('should return intent error on workflow_dispatch with no event_name override', () => {
  const savedContext = global.context;
  global.context = {
    ...global.context,
    eventName: 'workflow_dispatch',
    payload: { inputs: {} }, // no event_name, no aw_context
  };
  try {
    const result = handlers.addCommentHandler({ body: 'A real comment body that is substantive' });
    expect(result.isError).toBe(true);
    const data = JSON.parse(result.content[0].text);
    expect(data.error).toContain('"workflow_dispatch"');
    expect(mockAppendSafeOutput).not.toHaveBeenCalled();
  } finally {
    global.context = savedContext;
  }
});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 132323f. Added a bare workflow_dispatch (no event_name, no aw_context) test for each of the three handlers. All three verify isError: true with the error containing "workflow_dispatch" and appendSafeOutput not called.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 82/100 — Excellent

Analyzed 16 new test(s) across 2 JavaScript files: 15 design, 1 implementation, 0 guideline violation(s).

📊 Metrics & Test Classification (16 tests analyzed)
Metric Value
New/modified tests analyzed 16
✅ Design tests (behavioral contracts) 15 (94%)
⚠️ Implementation tests (low value) 1 (6%)
Tests with error/edge cases 13 (81%)
Duplicate test clusters 0
Test inflation detected Yes — both files >2:1 (contextually justified; see note)
🚨 Coding-guideline violations 0
Test File Classification Issues Detected
addCommentHandler: should return intent error when target is triggering (default) and not in issue/PR/discussion context safe_outputs_handlers.test.cjs:1819 ✅ Design
addCommentHandler: should return intent error on schedule event with default target safe_outputs_handlers.test.cjs:1835 ✅ Design
addCommentHandler: should write entry when target is triggering and in PR context safe_outputs_handlers.test.cjs:1851 ✅ Design
addCommentHandler: should write entry when explicit item_number bypasses context check safe_outputs_handlers.test.cjs:1865 ✅ Design
addCommentHandler: should write entry on workflow_dispatch with issue aw_context safe_outputs_handlers.test.cjs:1877 ✅ Design
updatePullRequestHandler: should return intent error when target is triggering (default) and not in PR context safe_outputs_handlers.test.cjs:2467 ✅ Design
updatePullRequestHandler: should return intent error on schedule event with default target safe_outputs_handlers.test.cjs:2483 ✅ Design
updatePullRequestHandler: should write entry and return success when target is "*" regardless of non-PR context safe_outputs_handlers.test.cjs:2499 ✅ Design
updatePullRequestHandler: should write entry and return success on workflow_dispatch with PR aw_context safe_outputs_handlers.test.cjs:2511 ✅ Design
updateIssueHandler: should return intent error when target is triggering (default) and not in issue context safe_outputs_handlers.test.cjs:2540 ✅ Design
updateIssueHandler: should return intent error on schedule event with default target safe_outputs_handlers.test.cjs:2551 ✅ Design
updateIssueHandler: should write entry and return success when in issue context with default target safe_outputs_handlers.test.cjs:2567 ✅ Design
updateIssueHandler: should write entry and return success when target is "*" regardless of non-issue context safe_outputs_handlers.test.cjs:2581 ✅ Design
updateIssueHandler: should write entry and return success on workflow_dispatch with issue aw_context safe_outputs_handlers.test.cjs:2593 ✅ Design
createStandardResolveNumber: should propagate shouldFail:false when not in issue context (schedule event) update_handler_factory.test.cjs:~878 ✅ Design
createUpdateHandlerFactory: should return skipped result when resolveItemNumber returns shouldFail:false update_handler_factory.test.cjs:~904 ⚠️ Implementation All 4 factory dependencies mocked with vi.fn(); test verifies orchestration path rather than an externally observable output

Go: 0; JavaScript: 16 (*.test.cjs).

Scoring breakdown:

  • Behavioral coverage: 37.5/40 (15/16 design tests)
  • Error/edge case coverage: 24.4/30 (13/16 tests — 8 error-path assertions + 5 edge-case paths: explicit bypass, wildcard target overrides, workflow_dispatch aw_context)
  • Low duplication: 20/20 (0 duplicate clusters; parallel tests for 3 handlers covering the same routing policy are not flagged as copies)
  • Proportional growth: 0/10 (inflation penalty — see note below)

⚠️ Test inflation note: Both files exceed the 2:1 growth ratio (safe_outputs_handlers.test.cjs 361 test lines / 102 production lines = 3.5:1; update_handler_factory.test.cjs 46 / 12 = 3.8:1). This is contextually justified: the fix introduces context-aware validation across 3 handlers × multiple event types × success/error/edge-case paths. The deduction is a rulebook artefact, not a quality concern.

Mock usage: vi.fn() is used for GitHub Actions runtime globals (core, github, context) — legitimate external runtime stubs. mockAppendSafeOutput represents the side-effect accumulation pipeline and is the observable output asserted in all 14 safe_outputs tests. The L904 factory test is the only case where 4 internal business-logic dependencies are injected as stubs, prompting the implementation classification.

⚠️ Flagged Tests — Requires Review (1 issue)

createUpdateHandlerFactory: should return skipped result when resolveItemNumber returns shouldFail:false (update_handler_factory.test.cjs:~904) — ⚠️ Implementation: all four factory dependencies (resolveItemNumber, buildUpdateData, executeUpdate, formatSuccessResult) are replaced with vi.fn() stubs. The test verifies internal orchestration behaviour (result.skipped === true, mockCore.info called, mockCore.warning not called) rather than an externally observable handler output. Note: the behavioural contract being tested (soft-skip vs hard-fail, info-level logging) is meaningful and directly covers the PR fix; refactoring as a higher-level integration test (calling the real createStandardResolveNumber with a schedule context) would improve fidelity.

Verdict

Check passed. 6% implementation tests (threshold: 30%). Design tests directly verify the fix's behavioral contracts: informative error responses when triggering context is absent, successful writes when context is correct, and soft-skip (non-failing) behaviour for schedule and push events across all three affected handlers.

🧪 Test quality analysis by Test Quality Sentinel ·

@github-actions github-actions Bot left a comment

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 Quality Sentinel: 82/100. Test quality is acceptable — 6% of new tests are implementation tests (threshold: 30%).

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

Copilot AI and others added 2 commits June 18, 2026 13:29
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
…dlers

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@pelikhan pelikhan merged commit 5bea59a into main Jun 18, 2026
14 checks passed
@pelikhan pelikhan deleted the copilot/aw-failures-fix-schedule-workflow branch June 18, 2026 13:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants