Skip to content

Prevent Copilot SDK event-log failures from terminating agent runs - #55500

Closed
pelikhan with Copilot wants to merge 5 commits into
mainfrom
copilot/fix-github-copilot-cli-crash
Closed

Prevent Copilot SDK event-log failures from terminating agent runs#55500
pelikhan with Copilot wants to merge 5 commits into
mainfrom
copilot/fix-github-copilot-cli-crash

Conversation

Copilot AI commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Copilot workflows could exit with code 1 when the SDK failed to create its auxiliary JSONL event-log directory. This telemetry failure was retried repeatedly and terminated otherwise valid agent sessions.

  • Resilient event logging

    • Treat event-log directory and stream failures as non-fatal.
    • Continue emitting SDK events to stderr for diagnostics.
    • Report the unavailable log path and underlying error.
    • Handle asynchronous stream errors without crashing the session.
  • Regression coverage

    • Reproduce an unavailable event-log parent path.
    • Verify the session completes successfully and events remain available on stderr.

Generated by 👨‍🍳 PR Sous Chef · pi · gpt54 · 10.3 AIC · ⌖ 9.22 AIC · ⊞ 6.2K ·
Comment /souschef to run again

Copilot AI and others added 2 commits August 24, 2026 20:27
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix GitHub Copilot CLI generic exit-1 crash across workflows Prevent Copilot SDK event-log failures from terminating agent runs Aug 24, 2026
Copilot AI requested a review from pelikhan August 24, 2026 20:30
@pelikhan
pelikhan marked this pull request as ready for review August 24, 2026 22:01
Copilot AI balanced review requested due to automatic review settings August 24, 2026 22:01
@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

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

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

🏗️ ADR gate enforced by Design Decision Gate 🏗️

@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

Test Quality Sentinel skipped because pre-fetch PR data was unavailable: unable to fetch test file diff

🧪 Test quality analysis by Test Quality Sentinel

@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Ponytail Reviewer completed successfully!

Generated by Ponytail Reviewer for #55500

@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

Reviewed PR #55500 and found no actionable changed-line issues to comment on; no GitHub write other than completion signaling needed.

🔎 Code quality review by PR Code Quality Reviewer

@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

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

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

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

Makes Copilot SDK event-log failures non-fatal while preserving stderr diagnostics.

Changes:

  • Handles directory and asynchronous stream errors.
  • Adds regression coverage for unavailable directories.
Show a summary per file
File Description
actions/setup/js/copilot_sdk_session.cjs Adds resilient event logging.
actions/setup/js/copilot_sdk_driver.test.cjs Tests synchronous directory failure.

Review details

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment on lines +90 to +92
it("continues when the SDK event log directory is unavailable", async () => {
const unavailableBase = path.join(testSessionStateDir, "not-a-directory");
fs.writeFileSync(unavailableBase, "file");

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.

Added a new test "continues when the SDK event log stream fails asynchronously" that pre-creates events.jsonl as a directory so createWriteStream succeeds synchronously but fails asynchronously with EISDIR. It asserts exit code 0, the SDK event log write failed warning, and the stderr event.

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

L271-280: shrink: split event-log setup into a tiny helper that returns a stream or null. That would collapse the duplicate warning branches and remove the extra mutable state around eventsStream.
net: -8 lines possible.

Generated by ✂️ Ponytail Reviewer for #55500 · codex · mai10 · 4.97 AIC · ⌖ 1.97 AIC · ⊞ 16.7K
Comment /ponytail to run again

// Snapshot to a non-null local for closure-safe writes (JSDoc nullability narrowing).
const stream = eventsStream;
log(`serialising SDK events to ${eventsPath}`);
try {

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.

L271-280: shrink: split event-log setup into a tiny helper that returns a stream or null. That would collapse the duplicate warning branches and remove the extra mutable state around eventsStream.

@github-actions github-actions Bot mentioned this pull request Aug 24, 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.

Skills-Based Review 🧠

Applied /diagnosing-bugs and /tdd — requesting changes on two correctness issues in the session file.

📋 Key Themes & Highlights

Key Themes

  • Resource leak (copilot_sdk_session.cjs:275): eventsStream is nulled on error but never .destroy()-ed, leaking the underlying file descriptor for the process lifetime.
  • Error handler ordering (copilot_sdk_session.cjs:281): If any code between createWriteStream and .on('error', ...) throws, an unhandled 'error' event could still crash the process — defeating the resilience goal.
  • Test assertion ordering (copilot_sdk_driver.test.cjs:126): Minor improvement — ordering assertions by importance improves failure message clarity.

Positive Highlights

  • ✅ Root cause correctly identified and addressed (non-fatal telemetry path).
  • ✅ Regression test added that covers the exact failure scenario end-to-end.
  • eventsStream?.write(jsonl) optional-chain is clean and safe.
  • ✅ stderr fallback ensures event observability is preserved even when the file path is unavailable.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · copilot · sonnet46 · 26.5 AIC · ⌖ 10.1 AIC · ⊞ 7.6K
Comment /matt to run again

fs.mkdirSync(sessionDir, { recursive: true });
eventsStream = fs.createWriteStream(eventsPath, { flags: "a" });
eventsStream.on("error", err => {
log(`warning: SDK event log write failed at ${eventsPath}: ${getErrorMessage(err)}; continuing with stderr event stream`);

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] Stream resource leak: when the "error" event fires, eventsStream is set to null but .destroy() is never called — the underlying file descriptor is abandoned for the lifetime of the process.

💡 Suggested fix
eventsStream.on("error", err => {
  log(`warning: SDK event log write failed at ${eventsPath}: ${getErrorMessage(err)}; continuing with stderr event stream`);
  const s = eventsStream;
  eventsStream = null;
  s?.destroy(); // release the underlying fd
});

Node.js WriteStream holds an open file descriptor until explicitly destroyed. Long-running agent sessions may accumulate leaked fds.

@copilot please address this.

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 latest commit — the error handler now snapshots the stale stream and calls .destroy() before nulling eventsStream.

log(`serialising SDK events to ${eventsPath}`);
} catch (err) {
log(`warning: SDK event log unavailable at ${eventsPath}: ${getErrorMessage(err)}; continuing with stderr event stream`);
}

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] The eventsStream variable is set inside a try block but still referenced in the writeEvent closure declared after it. If mkdirSync succeeds but createWriteStream throws, eventsStream remains null — this is fine. However if createWriteStream succeeds and the subsequent on("error", ...) call is what throws, eventsStream would be non-null but without an error handler, causing an unhandled 'error' event that terminates the process. Consider moving the on('error') registration directly after the createWriteStream call with no intervening code that could throw.

@copilot please address this.

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.

The on("error") handler is already registered immediately after createWriteStream, with no intervening code that could throw, so this ordering concern doesn't apply as written.

sessionStateBaseDir: unavailableBase,
sdkModule: {
CopilotClient: FakeCopilotClient,
RuntimeConnection: { forUri: vi.fn(() => ({})) },

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 test asserts logs contains a string matching "SDK event log unavailable" but never verifies that the session output reached the caller via the normal path — i.e., that result.output was populated from the in-memory event (not only from a file). The test already fires an assistant.message event, so adding expect(result.output).toContain("completed without event file") (which is present) is good. Consider also asserting result.exitCode === 0 before the stderrWriteSpy assertion so a non-zero exit surfaces a clearer failure message.

💡 Suggested assert order
expect(result.exitCode).toBe(0);           // 1. session completed
expect(result.output).toContain('...');    // 2. output propagated
expect(logs).toContainEqual(...);          // 3. warning logged
expect(stderrWriteSpy).toHaveBeenCalled...// 4. stderr fallback

This ordering matches Arrange→Act→Assert importance and gives better failure messages.

@copilot please address this.

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.

The existing test already asserts exitCode first, then output, then the log warning, then the stderr event — matching the suggested order.

@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: Prevent Copilot SDK event-log failures from terminating agent runs

The fix is clean and well-scoped. Wrapping mkdirSync/createWriteStream in a try-catch and gracefully degrading to stderr-only logging is the right approach. The new test covers the primary failure path.

One non-blocking suggestion: call .destroy() on the stale stream reference before nulling it in the error handler to avoid an open file-handle leak (see inline comment).

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · copilot · sonnet46 · 20.8 AIC · ⌖ 9.08 AIC · ⊞ 6.2K

log(`warning: SDK event log write failed at ${eventsPath}: ${getErrorMessage(err)}; continuing with stderr event stream`);
eventsStream = null;
});
log(`serialising SDK events to ${eventsPath}`);

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.

Non-blocking (harden): When the stream emits an error, eventsStream is set to null but the underlying stream object is not explicitly destroyed. This leaves an open file handle until GC collects it. Consider calling .destroy() on the stale reference before nulling it:

eventsStream.on("error", err => {
  log(`warning: SDK event log write failed at ${eventsPath}: ${getErrorMessage(err)}; continuing with stderr event stream`);
  const stale = eventsStream;
  eventsStream = null;
  stale?.destroy();
});

@copilot please address this.

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 latest commit — the error handler now snapshots the stale stream and calls .destroy() before nulling eventsStream.

@github-actions

Copy link
Copy Markdown
Contributor

Comment Memory

reviewed_at: 2026-08-24T00:00:00Z
review_event: COMMENT
top_themes:
  - event-log failure handling looks intentionally non-fatal
  - no changed-line correctness regressions identified
files_reviewed:
  - actions/setup/js/copilot_sdk_driver.test.cjs
  - actions/setup/js/copilot_sdk_session.cjs
comment_count: 0

Note

This comment is managed by comment memory.

It stores persistent context for this thread in the code block at the top of this comment.
Edit only the text inside the backtick fences; workflow metadata and the footer are regenerated automatically.

Learn more about comment memory

🔎 Code quality review by PR Code Quality Reviewer · pi · gpt54 · 4.67 AIC · ⌖ 6.74 AIC · ⊞ 7K ·
Comment /review to run again

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot This PR looks ready for forward progress. Please review the latest feedback, refresh the branch if needed, and run the pr-finisher skill before handing back for maintainer review.

Generated by 👨‍🍳 PR Sous Chef · pi · gpt54 · 10.3 AIC · ⌖ 9.22 AIC · ⊞ 6.2K ·
Comment /souschef to run again

…-error test coverage

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
@pelikhan pelikhan closed this Aug 24, 2026
Copilot stopped work on behalf of gh-aw-bot due to an error August 24, 2026 22:39
Copilot AI requested a review from gh-aw-bot August 24, 2026 22:39
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.

[aw-failures] GitHub Copilot CLI generic exit-1 crash across 7 workflows

4 participants