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
2 changes: 1 addition & 1 deletion docs/node-maintenance.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ installation, validation, and the audit result:
| Dependency changes with failed installation or checks | Retains a draft proposal when possible and fails the workflow. |
| Incomplete or unavailable security audit | Fails the run and skips publication, even if project checks pass; retains the diagnostic summary and report artifact. |
| No dependency changes | Creates no PR and leaves any existing proposal untouched; read the current run summary or report artifact. |
| Invalid configuration | Stops before dependency changes and updates one reusable configuration issue. Successful preflight closes it. |
| Invalid configuration | Stops before dependency changes and updates one reusable configuration issue. Successful preflight adds a verification comment and closes it, preserving the last failure description. |

Choose **upgrades** for a separate manual run when you want routine updates.
It also checks security and still obeys the cooldown and upgrade scope. It does
Expand Down
7 changes: 6 additions & 1 deletion node/runtime/configuration_issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def display(value, limit):
lines.extend(
[
"",
"Align the named project declarations, or explicitly override the inherited settings in `.github/node-maintenance.json`, then rerun **Node dependency audit**. No project files were rewritten.",
"Align the named project declarations, or explicitly override the inherited settings in `.github/node-maintenance.json`, then rerun your dependency maintenance workflow. No project files were rewritten.",
]
)
lines.extend(
Expand Down Expand Up @@ -170,6 +170,11 @@ def update_issue(repository, report, run_url, request=github_request):
body=issue_body(report, run_url),
failing=report["status"] == "error",
request=request,
resolution_comment=(
"Configuration preflight now passes.\n\n"
"The original failure description is preserved above.\n\n"
f"[Verification run]({run_url})"
),
)


Expand Down
67 changes: 65 additions & 2 deletions node/tests/test_configuration_issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,31 @@ def test_one_issue_across_failure_updates_resolution_and_recurrence():
"operation": "updated",
"number": 77,
}
failure_body = github.issues[0]["body"]
assert update(github, "ok", 3) == {"operation": "closed", "number": 77}
assert github.issues[0]["state"] == "closed"
assert github.issues[0]["body"] == failure_body
assert len(github.comments) == 1
assert "Configuration preflight now passes." in github.comments[0]["body"]
assert "https://github.com/owner/site/actions/runs/3" in github.comments[0]["body"]
assert update(github, "ok", 4) == {"operation": "unchanged", "number": 77}
assert update(github, run=5) == {"operation": "reopened", "number": 77}
assert github.issues[0]["state"] == "open"
assert len(github.issues) == 1
assert sum(method == "POST" for method, _, _ in github.calls) == 1
assert all("/comments" not in path for _, path, _ in github.calls)
assert (
sum(
method == "POST" and path == "repos/owner/site/issues"
for method, path, _ in github.calls
)
== 1
)
assert (
sum(
method == "POST" and path.endswith("/comments")
for method, path, _ in github.calls
)
== 1
)


def test_healthy_installation_does_not_create_an_issue_and_pending_does_not_close_one():
Expand Down Expand Up @@ -243,3 +260,49 @@ def test_reporting_job_is_serialized_and_has_no_consumer_checkout_or_app_key():
i for i, step in enumerate(steps) if step.get("id") == "proposal"
)
assert upload_index < proposal_index


def test_configuration_recovery_keeps_failure_open_if_comment_cannot_be_written():
github = GitHub()
update(github)
before = copy.deepcopy(github.issues)

def request(method, path, payload=None):
if method == "POST" and path.endswith("/comments"):
raise ValueError("comment unavailable")
return github.request(method, path, payload)

with pytest.raises(ValueError, match="comment unavailable"):
module["update_issue"](
"owner/site",
report("ok"),
"https://github.com/owner/site/actions/runs/2",
request,
)
assert github.issues == before
assert not any(method == "PATCH" for method, _, _ in github.calls)


def test_configuration_recovery_reuses_comment_after_close_request_failure():
github = GitHub()
update(github)
failure_body = github.issues[0]["body"]

def request(method, path, payload=None):
if method == "PATCH" and payload == {"state": "closed"}:
raise ValueError("close unavailable")
return github.request(method, path, payload)

with pytest.raises(ValueError, match="close unavailable"):
module["update_issue"](
"owner/site",
report("ok"),
"https://github.com/owner/site/actions/runs/2",
request,
)
assert github.issues[0]["state"] == "open"
assert github.issues[0]["body"] == failure_body
assert len(github.comments) == 1
assert update(github, "ok", 3) == {"operation": "closed", "number": 77}
assert github.issues[0]["body"] == failure_body
assert len(github.comments) == 1
Loading