Skip to content

feat(eslint): add require-await-core-summary-write rule#43170

Merged
pelikhan merged 5 commits into
mainfrom
copilot/eslint-miner-add-require-await-core-summary-write
Jul 4, 2026
Merged

feat(eslint): add require-await-core-summary-write rule#43170
pelikhan merged 5 commits into
mainfrom
copilot/eslint-miner-add-require-await-core-summary-write

Conversation

Copilot AI commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

core.summary.write() returns Promise<Summary>. Calling it without await silently discards the promise — if the Node.js process exits before the microtask queue drains, the step summary is truncated or missing entirely. Five existing violations in actions/setup/js demonstrate the pattern.

New ESLint rule: require-await-core-summary-write

  • Flags bare ExpressionStatement calls to .summary.write() — returned/assigned calls propagate the promise and are not flagged
  • Detected patterns: core.summary.write(), core.summary.addRaw(x).write(), deep chains like core.summary.addHeading(...).addRaw(x).write(), any coreAlias.summary.write(), and computed access core.summary["write"]()
  • Suggestion: inserts await before the expression, only offered when inside an async function (applying it outside would produce a syntax error)
  • Registered in eslint-factory/src/index.ts and enabled at "warn" in eslint.config.cjs
  • 9 tests covering valid, invalid, chained, computed, alias, non-async-no-suggestion, and suggestion output cases
// flagged — Promise<Summary> silently dropped
core.summary.addRaw(markdown).write();

// correct
await core.summary.addRaw(markdown).write();

Existing violations fixed

Added await to all 5 un-awaited call sites across parse_mcp_scripts_logs.cjs, log_parser_bootstrap.cjs (×2), parse_firewall_logs.cjs, parse_mcp_gateway_log.cjs, and upload_assets.cjs.


Generated by 👨‍🍳 PR Sous Chef · 7.05 AIC · ⌖ 5.18 AIC · ⊞ 6.6K ·
Comment /souschef to run again

Copilot AI and others added 2 commits July 3, 2026 10:38
…tions

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

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Add require-await-core-summary-write rule for ESLint feat(eslint): add require-await-core-summary-write rule Jul 3, 2026
Copilot AI requested a review from pelikhan July 3, 2026 10:42
@github-actions github-actions Bot mentioned this pull request Jul 3, 2026
@pelikhan
pelikhan marked this pull request as ready for review July 3, 2026 12:50
Copilot AI review requested due to automatic review settings July 3, 2026 12:50
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

🔎 PR Code Quality Reviewer is reviewing code quality for this pull request...

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer is reviewing this pull request using Matt Pocock's engineering skills...

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

🔬 Test Quality Sentinel is analyzing test quality on this pull request...

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

🔍 Design Decision Gate 🏗️ is checking for design decision records on this pull request...

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 adds a custom ESLint rule to prevent un-awaited core.summary.write() calls (which return a Promise<Summary> and can be dropped/truncated if the process exits too early), and updates existing workflow helper scripts to await those summary writes.

Changes:

  • Added a new ESLint rule require-await-core-summary-write (with tests) that flags bare expression-statement .summary.write() calls and offers an await suggestion in async contexts.
  • Registered and enabled the rule at "warn" in the eslint-factory plugin/config.
  • Updated multiple actions/setup/js/*.cjs scripts to await summary writes.
Show a summary per file
File Description
eslint-factory/src/rules/require-await-core-summary-write.ts Implements the new rule that detects un-awaited .summary.write() calls on summary chains and offers an await suggestion in async functions.
eslint-factory/src/rules/require-await-core-summary-write.test.ts Adds test coverage for valid/invalid patterns, chaining, computed access, aliasing, and suggestion behavior.
eslint-factory/src/index.ts Registers the new rule in the eslint-factory plugin export.
eslint-factory/eslint.config.cjs Enables the new rule at warning level.
actions/setup/js/upload_assets.cjs Awaits core.summary.write() to ensure summary output is flushed reliably.
actions/setup/js/parse_mcp_scripts_logs.cjs Awaits core.summary.addRaw(...).write() to avoid dropping the Promise.
actions/setup/js/parse_mcp_gateway_log.cjs Attempts to await summary writes, but currently introduces a syntax error due to await in a non-async function.
actions/setup/js/parse_firewall_logs.cjs Awaits core.summary.addRaw(...).write() to avoid dropping the Promise.
actions/setup/js/log_parser_bootstrap.cjs Awaits core.summary.addRaw(...).write() at both call sites.

Review details

Tip

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

  • Files reviewed: 9/9 changed files
  • Comments generated: 1
  • Review effort level: Low

Comment on lines 233 to 237
coreObj.summary.addRaw(timelineMd);
}

coreObj.summary.write();
await coreObj.summary.write();
}
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 82/100 — Excellent

Analyzed 9 test(s): 8 design, 1 implementation, 0 violation(s).

i️ Note: The pre-fetch scripts target .test.cjs/.test.js patterns; the new TypeScript test (require-await-core-summary-write.test.ts) was not captured by those patterns. Test functions were analyzed directly from the source file.

📊 Metrics (9 tests)
Metric Value
Analyzed 9 (Go: 0, JS/TS: 9)
✅ Design 8 (89%)
⚠️ Implementation 1 (11%)
Edge/error coverage 5 (56%)
Duplicate clusters 0
Inflation No (1.29:1)
🚨 Violations 0
Test File Classification Issues
uses the correct docs URL require-await-core-summary-write.test.ts:13 implementation_detail None — validates rule wiring but not behavioral contract
valid: awaited calls are not flagged :17 design_test None
valid: returned and assigned calls are not flagged :29 design_test Edge case: verifies Promise-propagating patterns aren't flagged
invalid: bare core.summary.write() is flagged :42 design_test Core behavioral contract
invalid: chained ...addRaw(x).write() is flagged :54 design_test Edge case: method-chain patterns
invalid: coreObj alias and computed access are flagged :70 design_test Edge case: alias + computed member access
valid: unrelated .write() calls are not flagged :86 design_test Edge case: false-positive guard (fs.write, stream.write, etc.)
invalid: flagged outside async function — no suggestion offered :93 design_test Edge case: non-async context, no fixer suggestion
suggestion: inserts 'await ' before the expression :111 design_test Verifies fixer output for three representative patterns

Verdict

Passed. 11% implementation tests (threshold: 30%). No guideline violations. The suite covers the rule's behavioral contract thoroughly — valid patterns, invalid patterns, chained calls, aliases, computed access, false-positive guards, and async-context fixer output. Minor note: the uses the correct docs URL test verifies rule metadata rather than behavioral output; not harmful, just low-value.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

🧪 Test quality analysis by Test Quality Sentinel ·
Comment /review to run again

@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. 11% implementation tests (threshold: 30%). No guideline violations.

@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.

Review: feat(eslint): add require-await-core-summary-write rule

The new ESLint rule and test suite are well-structured — the rule correctly targets bare ExpressionStatement call sites, propagated usages (return/assign) are rightly exempted, and the suggestion correctly guards itself behind isInsideAsyncFunction. The five caller-site fixes in the .cjs action scripts are also correct in principle.

However, there is one blocking correctness bug introduced by this PR.

🔴 Blocking: await inside a non-async function in parse_mcp_gateway_log.cjs

writeStepSummaryWithTokenUsage at line 204 is not declared async, so the added await coreObj.summary.write() at line 236 is a SyntaxError and will crash the process before any step summary is written. This is a regression — the pre-PR code at least executed without crashing.

See the inline comment for the two-part fix needed (mark the function async, and await its three call sites in main()).


Non-blocking observations
  • Rule coverage: the rule only detects patterns rooted in a .summary member whose property name is summary. If a caller passes a summary object directly (e.g. writeStepSummary(core.summary) and then calls coreObj.write() inside), the rule won't fire — but that's an acceptable limitation and is consistent with the PR description.
  • rootsSummary traversal depth: the recursive walk handles core.summary.addX().addY().write() chains of arbitrary depth, which is correct and future-proof.
  • Test duplication: the "suggestion: inserts 'await' before the expression" test suite repeats cases already covered by earlier it blocks. Deduplication is minor but keeps the test file lean.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer

}

coreObj.summary.write();
await coreObj.summary.write();

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.

Bug: await used inside a non-async function — this will throw a SyntaxError at runtime.

writeStepSummaryWithTokenUsage is declared as a plain (non-async) function at line 204:

function writeStepSummaryWithTokenUsage(coreObj) {

Adding await coreObj.summary.write() inside it produces SyntaxError: await is only valid in async functions at parse time. The process will fail before writing the step summary.

Two changes are required:

  1. Mark the function async:
    async function writeStepSummaryWithTokenUsage(coreObj) {
  2. Await all three call sites in main() (~lines 907, 938, 977):
    await writeStepSummaryWithTokenUsage(core);

Without both changes the bug the rule was meant to fix (unawaited core.summary.write()) is traded for an outright syntax crash.

@copilot please address this.

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

🤖 PR Triage

Field Value
Category feature
Risk 🟡 Medium
Score 62/100 (Impact 32 + Urgency 16 + Quality 14)
Action fast_track

Score Breakdown

  • Impact (32/50): New ESLint rule require-await-core-summary-write catches silent promise-drop bugs; fixes 5 existing violations in actions/setup/js
  • Urgency (16/30): CI agent runs in progress; no reviews yet; created today
  • Quality (14/20): 9 files, 266 adds, clear description with concrete examples

Ready for human review once CI completes.

Generated by 🔧 PR Triage Agent · 92.3 AIC · ⌖ 6.73 AIC · ⊞ 5.5K ·

@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 crash-level bug must be fixed before merge

The ESLint rule itself is well-structured and the intent is correct, but the manual fix in parse_mcp_gateway_log.cjs introduces a runtime SyntaxError that will make the module unloadable. That alone blocks merge.

📋 Findings summary

🔴 Critical (1)

  • await in non-async function (parse_mcp_gateway_log.cjs:236): writeStepSummaryWithTokenUsage is a plain synchronous function. await here is a SyntaxError — Node.js will refuse to load the module. All three call sites in main() (lines 907, 938, 977) break. The ESLint rule deliberately withholds its await-insert suggestion in non-async contexts; this fix was applied manually, bypassing that guard. Fix: make the function async and await all three callers.

🟠 High (1)

  • rootsSummary false positives (require-await-core-summary-write.ts:27): The predicate matches any object with a .summary.write() method, not just @actions/core. db.summary.write(), logger.summary.write() etc. would all trigger the rule. Root identifier should be constrained to known core aliases.

🟡 Medium (2)

  • Unused node parameter in isInsideAsyncFunction — dead parameter implies false intent; prefix _node or remove it.
  • No invalid/suggestion test for async arrow or function expressionArrowFunctionExpression and FunctionExpression are in ASYNC_FUNCTION_TYPES but untested in the invalid path; a silent regression removing them would not be caught.

🔎 Code quality review by PR Code Quality Reviewer
Comment /review to run again

}

coreObj.summary.write();
await coreObj.summary.write();

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.

SyntaxError: await inside a non-async function — this module will crash at require-time.

Line 204 declares function writeStepSummaryWithTokenUsage(coreObj) — a regular synchronous function. Using await inside a non-async function is a SyntaxError in Node.js CJS; the module will fail to load entirely, breaking all three callers at lines 907, 938, and 977 in main().

💡 Suggested fix

Make the function async and update all three call sites to await it:

async function writeStepSummaryWithTokenUsage(coreObj) {
  // ... existing body ...
  await coreObj.summary.write();
}

// In main() — all three call sites (lines 907, 938, 977):
await writeStepSummaryWithTokenUsage(core);

Verified with Node.js:

SyntaxError: await is only valid in async functions and the top level bodies of modules

The new ESLint rule correctly refuses to offer the await suggestion when outside an async function. The manual fix was applied anyway, bypassing that guard.

* - `core.summary.addHeading(...).addRaw(x)`
* - `coreObj.summary` (any identifier alias)
*/
function rootsSummary(node: TSESTree.Node): 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.

rootsSummary matches any object with .summary.write() — not just @actions/core — producing false positives on unrelated code.

The function returns true for any MemberExpression whose property is summary, regardless of the root identifier. This means db.summary.write(), logger.summary.write(), document.summary.write() would all be flagged even though they have nothing to do with GitHub Actions.

💡 Suggested fix

Constrain the root to a known core-like identifier. One approach: check that the deepest object in the chain is an Identifier whose name matches a configurable set (default: ["core", "coreObj"]):

const DEFAULT_CORE_NAMES = new Set(["core", "coreObj"]);

function getRootIdentifier(node: TSESTree.Node): string | null {
  if (node.type === "Identifier") return node.name;
  if (node.type === "MemberExpression") return getRootIdentifier(node.object);
  if (node.type === "CallExpression") return getRootIdentifier(node.callee);
  return null;
}

function rootsSummary(node: TSESTree.Node, coreNames: Set<string>): boolean {
  const root = getRootIdentifier(node);
  if (root === null || !coreNames.has(root)) return false;
  // ... existing summary property check ...
}

Alternatively, add a rule option coreNames: string[] (schema) so projects using non-standard import aliases can configure it.

Without this constraint, any library using .summary.write() as a non-Promise method will get spurious warnings.

* whether `await` is currently valid — the suggestion is only safe to apply
* in an async context.
*/
function isInsideAsyncFunction(node: TSESTree.Node, ancestors: TSESTree.Node[]): 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.

isInsideAsyncFunction first parameter node is unused dead code.

The function body only ever iterates ancestors; node is never read. This creates a misleading API that implies node participates in the scope calculation.

💡 Suggested fix

Either remove the parameter entirely or prefix it with _ to signal intentional discard (matching TypeScript/ESLint conventions):

// Option A — remove it
function isInsideAsyncFunction(ancestors: TSESTree.Node[]): boolean {

// Option B — mark as intentionally unused
function isInsideAsyncFunction(_node: TSESTree.Node, ancestors: TSESTree.Node[]): boolean {

Update the one call site accordingly:

const suggest = isInsideAsyncFunction(ancestors) ? [...] : [];

});
});

it("invalid: bare core.summary.write() is flagged", () => {

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.

No test for async arrow function or async function expression in the invalid path — a regression in ASYNC_FUNCTION_TYPES would go undetected.

Every invalid test case uses async function f() {...} (FunctionDeclaration only). ArrowFunctionExpression and FunctionExpression are in ASYNC_FUNCTION_TYPES and assumed to work, but removing either would not break any test.

💡 Suggested fix

Add at least these two invalid cases (one per missing function form):

// async arrow function
{
  code: `const f = async () => { core.summary.write(); };`,
  errors: [{ messageId: "requireAwait", suggestions: [{ messageId: "addAwait", output: `const f = async () => { await core.summary.write(); };` }] }],
},
// async function expression
{
  code: `const f = async function() { core.summary.write(); };`,
  errors: [{ messageId: "requireAwait", suggestions: [{ messageId: "addAwait", output: `const f = async function() { await core.summary.write(); };` }] }],
},

Also worth adding: a non-async arrow const f = () => { core.summary.write(); }; should be flagged with no suggestion (same as the non-async FunctionDeclaration case already tested).

@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 /diagnosing-bugs, /tdd — requesting changes on one correctness bug and two test-quality issues.

📋 Key Themes & Highlights

Key Themes

  • Critical correctness bug: await is inserted into writeStepSummaryWithTokenUsage in parse_mcp_gateway_log.cjs, which is not an async function — this is a syntax error that will crash the Action.
  • False positive surface: rootsSummary flags any .summary.write() call regardless of the receiver object, which will produce false positives on non-core objects.
  • Test fixture issues: one fixture uses an undeclared variable (p = core.summary.write()), and an async IIFE pattern is not exercised.

Positive Highlights

  • ✅ Excellent motivation: the PR description clearly explains the root cause and the truncation risk.
  • ✅ The suggestion logic is safe — it correctly withholds the await fix outside async contexts.
  • ✅ Comprehensive test coverage across chained calls, computed access, and alias patterns.
  • ✅ Five real violations fixed in a single PR — proves the rule finds real bugs immediately.
  • ✅ Clean integration into the existing plugin structure (index.ts, eslint.config.cjs).

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer
Comment /matt to run again

}

coreObj.summary.write();
await coreObj.summary.write();

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.

[/diagnosing-bugs] await is added inside writeStepSummaryWithTokenUsage which is not an async function — this is a syntax error in a CJS module and will crash the Action at runtime.

💡 Correct fix

The ESLint rule's own suggestion logic correctly withholds the await fix outside async contexts; this change should do the same. You need to either make the function async (and propagate the change to all callers), or return the promise:

// Option A — make the function async
async function writeStepSummaryWithTokenUsage(coreObj) {
  // ...
  await coreObj.summary.write();
}
// then await every call site

// Option B — return the Promise (no change to callers needed)
function writeStepSummaryWithTokenUsage(coreObj) {
  // ...
  return coreObj.summary.write();
}

The other four fixes (log_parser_bootstrap.cjs, parse_firewall_logs.cjs, parse_mcp_scripts_logs.cjs, upload_assets.cjs) are all inside async function main() and are correct.

@copilot please address this.

if (node.type === "CallExpression" && node.callee.type === "MemberExpression") {
return rootsSummary(node.callee.object);
}
return 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.

[/tdd] rootsSummary accepts any identifier with a .summary property — fs.summary.write(), db.summary.write(), etc. would all be flagged as false positives.

💡 Tighten the check + add tests

The check should also verify the receiver of .summary is a known alias for @actions/core. One safe approach: look for an Identifier whose name ends with core (or is in a configurable allow-list), instead of allowing any identifier:

// check the root of the chain is a known core identifier
function isCoreReceiver(node: TSESTree.Node): boolean {
  return node.type === 'Identifier' && /core/i.test(node.name);
}

And add test cases:

// should NOT be flagged
'fs.summary.write();',
'db.summary.write();',
'foo.bar.summary.write();',   // nested but no core ancestor

@copilot please address this.

errors: [{ messageId: "requireAwait", suggestions: [{ messageId: "addAwait", output: `async function f() { await core.summary.addRaw(summary).write(); }` }] }],
},
{
code: `async function f() { core.summary.addHeading("Title").addRaw(body).write(); }`,

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 valid: returned and assigned calls are not flagged test includes p = core.summary.write() but the test file declares p nowhere — this would throw a ReferenceError in strict mode and is an invalid test fixture.

💡 Fix the fixture

Either declare p first or use the var keyword so the test fixture is itself valid JS:

// declare p before assignment
`async function f() { let p; p = core.summary.write(); }`
// or use a VariableDeclaration to keep it simple
`async function f() { const p = core.summary.write(); }`

This also means the assignment-expression path is only tested by the const p = case in the same group, which already has full coverage. The undeclared-variable case can be dropped.

@copilot please address this.

});

it("invalid: coreObj alias and computed access are flagged", () => {
cjsRuleTester.run("require-await-core-summary-write", requireAwaitCoreSummaryWriteRule, {

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] Missing edge-case test: bare core.summary.write() inside a top-level async IIFE (async () => { ... })() — the suggestion should still be offered since the enclosing function is async.

💡 Add the test case
{
  code: `(async () => { core.summary.write(); })()`,
  errors: [{ messageId: "requireAwait", suggestions: [{ messageId: "addAwait", output: `(async () => { await core.summary.write(); })()` }] }],
},

isInsideAsyncFunction already walks ancestors so this should work out of the box, but an explicit test guards against future regressions.

@copilot please address this.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address these unresolved review items, and rerun checks once the branch is up to date:

  • github-actions:
  • github-actions:
  • github-actions:

Generated by 👨‍🍳 PR Sous Chef · 7.05 AIC · ⌖ 5.18 AIC · ⊞ 6.6K ·
Comment /souschef to run again

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill, address these unresolved review items, and rerun checks once the branch is up to date:

  • github-actions:
  • github-actions: ...

Addressed in 28ffa74. I fixed the non-async await regression in parse_mcp_gateway_log.cjs, narrowed the ESLint rule to core-like summary chains, and added the requested rule tests/fixture fixes. Local make fmt, make lint, make test-unit, make test, make agent-report-progress, and npm --prefix eslint-factory test all passed. CI on this new HEAD still needs a maintainer-triggered rerun because agent pushes do not restart workflows.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.82.3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[eslint-miner] feat(eslint): add require-await-core-summary-write rule

4 participants