Skip to content
This repository was archived by the owner on May 4, 2026. It is now read-only.

fix: resolve flaky test in CI by adding proper shutdown sequence - #25

Closed
teh-hippo with Claude wants to merge 2 commits into
mainfrom
claude/fix-hyperlink-issues
Closed

fix: resolve flaky test in CI by adding proper shutdown sequence#25
teh-hippo with Claude wants to merge 2 commits into
mainfrom
claude/fix-hyperlink-issues

Conversation

@Claude

@Claude Claude AI commented Apr 19, 2026

Copy link
Copy Markdown

Summary

Fixed a flaky test that was causing intermittent CI failures. The test_stdio_initialize_and_tools_list test had a ~20-30% failure rate due to a race condition.

Problem

The test would fail with StopIteration when trying to retrieve the tools/list response (id=2). This happened because:

  1. The test sent all input at once and closed stdin
  2. The MCP server saw EOF on stdin and began shutting down
  3. The server terminated before flushing the second response to stdout
  4. The test only received the initialize response (id=1) but not the tools/list response (id=2)

Solution

Added a proper MCP shutdown sequence to the test:

  • Send shutdown request (id=3)
  • Send notifications/exit notification
  • This ensures the server processes and flushes all pending responses before terminating

Also improved error messages to show which responses were actually received when assertions fail.

Testing

  • Reproduced the issue locally (3 failures out of 15 runs = 20% failure rate)
  • After fix: 90 consecutive test runs with 100% pass rate
  • All tests in the suite pass
  • Linter, formatter, and build all succeed

References

Fixes the test failure from: https://github.com/teh-hippo/tiny-ntfy-mcp/actions/runs/24599264157/job/71935021663

Claude AI and others added 2 commits April 19, 2026 04:01
Agent-Logs-Url: https://github.com/teh-hippo/tiny-ntfy-mcp/sessions/5a3ae47b-b9c3-46a7-bfee-44a450d0942f

Co-authored-by: teh-hippo <490309+teh-hippo@users.noreply.github.com>
The test_stdio_initialize_and_tools_list test was flaky due to a race
condition where the MCP server would see EOF on stdin and shut down
before flushing all responses to stdout. This caused the tools/list
response to be lost intermittently (~20-30% failure rate).

The fix adds a proper shutdown sequence (shutdown request + exit
notification) before closing stdin, ensuring the server processes and
flushes all pending responses before terminating.

Tested with 90 consecutive runs - all passed (previously ~30% failed).

Agent-Logs-Url: https://github.com/teh-hippo/tiny-ntfy-mcp/sessions/5a3ae47b-b9c3-46a7-bfee-44a450d0942f

Co-authored-by: teh-hippo <490309+teh-hippo@users.noreply.github.com>
@teh-hippo
teh-hippo marked this pull request as ready for review April 19, 2026 04:07
@teh-hippo
teh-hippo requested a review from Copilot April 19, 2026 04:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Resolves a flaky CI test by ensuring the MCP server receives an explicit shutdown sequence so it flushes pending responses before exiting.

Changes:

  • Extend test_stdio_initialize_and_tools_list to send shutdown and notifications/exit after tools/list.
  • Switch the test runner from subprocess.run(...) to subprocess.Popen(...).communicate(...) and improve assertion error messages.
  • Update uv.lock to reflect tiny-ntfy-mcp version 2.0.9.

Reviewed changes

Copilot reviewed 1 out of 2 changed files in this pull request and generated 1 comment.

File Description
tests/test_mcp_server.py Adds shutdown/exit messages, changes subprocess invocation, and improves missing-response diagnostics.
uv.lock Updates locked editable package version to 2.0.9.

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

Comment thread tests/test_mcp_server.py
check=False,
)
assert proc.returncode == 0, proc.stderr
stdout, stderr = proc.communicate(input=input_text, timeout=5)

Copilot AI Apr 19, 2026

Copy link

Choose a reason for hiding this comment

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

proc.communicate(..., timeout=5) can raise subprocess.TimeoutExpired; unlike subprocess.run(timeout=...), this won’t automatically terminate the child process. If the timeout is hit (e.g., CI slowdown), the test can leak a running MCP server process and potentially impact subsequent tests. Wrap communicate() in a try/except (kill/terminate + second communicate/wait in finally) or switch back to subprocess.run(..., timeout=...) now that the shutdown/exit sequence is included.

Suggested change
stdout, stderr = proc.communicate(input=input_text, timeout=5)
try:
stdout, stderr = proc.communicate(input=input_text, timeout=5)
except subprocess.TimeoutExpired:
proc.kill()
stdout, stderr = proc.communicate()
pytest.fail(f"tiny_ntfy_mcp did not exit within timeout; stdout={stdout!r}, stderr={stderr!r}")
finally:
if proc.poll() is None:
proc.kill()
proc.wait()

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@anthropic-code-agent

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@openai-code-agent

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 2 changed files in this pull request and generated 1 comment.


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

Comment thread tests/test_mcp_server.py
Comment on lines +66 to 72
assert 2 in responses_by_id, f"Missing tools/list response (id=2). Got responses: {[r.get('id') for r in responses]}"

init_resp = responses_by_id[1]
tools_resp = responses_by_id[2]

assert init_resp["result"]["serverInfo"]["name"] == "tiny-ntfy-mcp"
assert init_resp["result"]["serverInfo"]["version"]

Copilot AI Apr 19, 2026

Copy link

Choose a reason for hiding this comment

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

This test now sends a shutdown request (id=3) but never asserts that the corresponding response was received. Adding an assertion for id=3 would ensure the graceful shutdown path actually ran (and would make failures easier to diagnose if shutdown handling regresses).

Suggested change
assert 2 in responses_by_id, f"Missing tools/list response (id=2). Got responses: {[r.get('id') for r in responses]}"
init_resp = responses_by_id[1]
tools_resp = responses_by_id[2]
assert init_resp["result"]["serverInfo"]["name"] == "tiny-ntfy-mcp"
assert init_resp["result"]["serverInfo"]["version"]
assert 2 in responses_by_id, f"Missing tools/list response (id=2). Got responses: {[r.get('id') for r in responses]}"
assert 3 in responses_by_id, f"Missing shutdown response (id=3). Got responses: {[r.get('id') for r in responses]}"
init_resp = responses_by_id[1]
tools_resp = responses_by_id[2]
shutdown_resp = responses_by_id[3]
assert init_resp["result"]["serverInfo"]["name"] == "tiny-ntfy-mcp"
assert init_resp["result"]["serverInfo"]["version"]
assert "result" in shutdown_resp

Copilot uses AI. Check for mistakes.
@teh-hippo

Copy link
Copy Markdown
Owner

Superseded by #28, which uses a more robust approach that also addresses the mcp>=1.27.0 transport-close cancellation behaviour (see modelcontextprotocol/python-sdk#2334). The shutdown-handshake fix here still races the new cancel-on-stdin-close logic in the MCP server. The two outstanding review comments on this PR are not applicable to #28: the timeout-leak concern is handled there by an explicit kill+wait in a finally block, and the id=3 shutdown response no longer needs asserting because #28 doesn't send a shutdown request.

@teh-hippo teh-hippo closed this May 4, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants