{"meta":{"title":"Diagnosing CI test failures","intro":"Use Copilot CLI to pull CI logs, correlate failures to local code, and fix issues without leaving the terminal.","product":"GitHub Copilot","breadcrumbs":[{"href":"/en/copilot","title":"GitHub Copilot"},{"href":"/en/copilot/tutorials","title":"Tutorials"},{"href":"/en/copilot/tutorials/copilot-cookbook","title":"GitHub Copilot Cookbook"},{"href":"/en/copilot/tutorials/copilot-cookbook/debug-errors","title":"Debug errors"},{"href":"/en/copilot/tutorials/copilot-cookbook/debug-errors/diagnose-ci-test-failures","title":"Diagnose CI test failures"}],"documentType":"article"},"body":"# Diagnosing CI test failures\n\nUse Copilot CLI to pull CI logs, correlate failures to local code, and fix issues without leaving the terminal.\n\nCopilot CLI ships with the GitHub MCP server, which gives it direct access to your GitHub Actions workflow runs, job logs, and check statuses. Combined with access to your local files, it can fetch CI failure details, correlate them to your code, and propose fixes from your terminal.\n\n## Example scenario 1: Tests pass locally but fail in CI\n\nYou have a test that passes on your local machine but fails in CI. You can ask Copilot CLI to investigate this test failure directly.\n\nIn this example, the code being tested defines a simple order service (`order.py`), and there is a corresponding test that checks if an order was created today (`test_order_service.py`).\n\n### Example prompt\n\n```copilot copy\nMy CI is failing on this branch. Can you pull the latest workflow run \nlogs, figure out what is failing, and help me fix it? The relevant files \nare @order.py and @test_order_service.py\n```\n\n### Example response\n\n> \\[!NOTE] The following response is an example. Copilot Chat responses are non-deterministic, so you may get a different response from the one shown here.\n\nCopilot CLI uses the GitHub MCP server to fetch your latest workflow runs on the current branch, identifies the failed job, and retrieves its logs. It finds the following failure:\n\n```text\n___ TestOrderService.test_order_created_today ___\n>       assert order[\"created_date\"] == date.today()\nE       AssertionError: assert datetime.date(2024, 1, 15) == datetime.date(2024, 1, 16)\n\ntest_order_service.py:45: AssertionError\n```\n\nAfter reading both local files, Copilot CLI notices that the dates are exactly one day apart and identifies this as a **timezone boundary issue**. The CI runner uses UTC while your local machine uses a different timezone. When the test runs near midnight, `date.today()` returns different dates in each environment.\n\nCopilot CLI suggests standardizing how `today` is computed by injecting a clock dependency or pinning the timezone:\n\n```python\nfrom datetime import timezone, datetime\n\ndef get_today():\n    return datetime.now(timezone.utc).date()\n```\n\nIt applies the fix to both `order.py` and `test_order_service.py`, updating all references to use the new `get_today()` helper.\n\nAfter the fix, you can verify the changes and run the tests locally:\n\n1. Run `/diff` to review exactly what Copilot CLI changed.\n2. Run `!pytest test_order_service.py` to confirm tests pass locally.\n3. Push the fix and let CI re-run.\n\n## Example scenario 2: Intermittent test failures with race conditions\n\nA test sometimes passes and sometimes fails on the same machine. You want Copilot CLI to compare logs from passing and failing runs to identify the cause.\n\nIn this example, the code under test uses a background job in `order_service.py` to update an order's status asynchronously, and a test in `test_order_service.py` asserts that the final status is `\"processed\"`.\n\n### Example prompt for race conditions\n\n```copilot copy\nThis test passes sometimes and fails sometimes. Can you pull the logs \nfrom the last passing and failing CI runs for test_order_processed \non this branch, compare them, and figure out what is going wrong? \nRelevant files: @order_service.py @test_order_service.py\n```\n\n### Example response for race conditions\n\n> \\[!NOTE] The following response is an example. Copilot Chat responses are non-deterministic, so you may get a different response from the one shown here.\n\nCopilot CLI uses the GitHub MCP server to list recent workflow runs on the current branch, identifies one passing and one failing run, and retrieves the logs for both. It compares them:\n\n**Passing run:**\n\n```text\n[DEBUG] Created order #1234\n[DEBUG] Background job started for order #1234\n[DEBUG] Background job completed (52ms)\n[DEBUG] Checking order status\n[DEBUG] Order #1234 status: processed\n```\n\n**Failing run:**\n\n```text\n[DEBUG] Created order #1234\n[DEBUG] Background job started for order #1234\n[DEBUG] Checking order status\n[DEBUG] Order #1234 status: pending\n```\n\nCopilot CLI spots that in the passing run, the background job completed before the status check, while in the failing run, the status was checked while the job was still running. It identifies this as a **race condition** because the test does not wait for the background job to finish.\n\nCopilot CLI suggests adding an explicit wait mechanism before asserting and proposes a fix using a polling helper:\n\n```python\nimport time\n\ndef wait_for_status(order_id, expected, timeout=5):\n    start = time.time()\n    while time.time() - start < timeout:\n        order = get_order(order_id)\n        if order.status == expected:\n            return order\n        time.sleep(0.1)\n    raise TimeoutError(\n        f\"Order {order_id} did not reach '{expected}' within {timeout}s\"\n    )\n```\n\n## Further reading\n\n* * [GitHub Copilot CLI](/en/copilot/how-tos/copilot-cli)"}