Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion scripts/workflow_startup_failure_diagnostic.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,19 @@
import subprocess
import sys
import time
from pathlib import Path
from typing import Any

from scripts import api_client
# Invoked BOTH as `python -m scripts.workflow_startup_failure_diagnostic` and as
# `python scripts/workflow_startup_failure_diagnostic.py` (health-40 and the
# INTEGRATION_GUIDE recipe use the file-path form). The path form leaves the repo
# root off sys.path, so `from scripts import api_client` raises
# ModuleNotFoundError. Same bootstrap the other ten scripts here use.
ROOT = Path(__file__).resolve().parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))

from scripts import api_client # noqa: E402


def _github_token() -> str:
Expand Down
37 changes: 37 additions & 0 deletions tests/scripts/test_workflow_startup_failure_diagnostic.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
from __future__ import annotations

import os
import subprocess
import sys
from pathlib import Path
from typing import Any

import pytest
Expand Down Expand Up @@ -770,3 +774,36 @@ def test_sweep_still_reports_when_nothing_executed_at_or_after_the_hold(monkeypa
report = diag.sweep(["owner/repo"], token="t", now="2026-08-23T06:00:00Z")

assert report["held_count"] == 1


def test_script_runs_when_invoked_by_path_without_pythonpath() -> None:
"""The invocation CI actually uses must import cleanly.

health-40 runs `python scripts/workflow_startup_failure_diagnostic.py --sweep`
and docs/INTEGRATION_GUIDE.md documents the same file-path form. Running a
file by path does NOT put the repo root on sys.path, so `from scripts import
api_client` raised ModuleNotFoundError and the liveness job failed on every
run - while every local check passed, because they were run with PYTHONPATH
set or via `python -m`.

The whole point of this module is to notice when a workflow stops executing.
Shipping it in a form that cannot execute was the same class of defect, so
this test exercises the real invocation with a clean environment rather than a
convenient one.
"""
root = Path(__file__).resolve().parents[2]
script = root / "scripts" / "workflow_startup_failure_diagnostic.py"
env = {k: v for k, v in os.environ.items() if k != "PYTHONPATH"}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Test the file-path invocation without undeclared packages

In the checked health-40-sweep.yml workflow, the liveness job only runs actions/setup-python before invoking this script; it never installs project dependencies. This test removes only PYTHONPATH, so under the normal pytest environment it inherits the installed requests package and passes, whereas a clean setup-python interpreter gets past the new path bootstrap and immediately fails in scripts/api_client.py with ModuleNotFoundError: No module named 'requests'. Thus the scheduled liveness job remains unable to run; either install the dependency in that job, remove the diagnostic's third-party runtime dependency, or exercise the subprocess with an actually clean Python environment.

Useful? React with 👍 / 👎.


result = subprocess.run(
[sys.executable, str(script), "--help"],
capture_output=True,
text=True,
env=env,
cwd=str(root),
timeout=60,
)

assert "ModuleNotFoundError" not in result.stderr, result.stderr
assert result.returncode == 0, result.stderr
assert "--sweep" in result.stdout
Loading