Repository navigation
feat: monitor service readiness and alert Discord - #283
pusheen5000000 wants to merge 3 commits into
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
qiuethan
left a comment
There was a problem hiding this comment.
Two reliability issues reproduced with offline fakes. Please address these before merge.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Independently rechecked with a Claude reviewer against head
Verification: the eight committed tests pass; offline checks covered separate-process JSON persistence, ordinary webhook rejection/429 handling, enabling a previously unset webhook, debounce, recovery retry, silencing, and the scenarios above. The two previously resolved findings remain fixed. All findings concern code introduced in this PR. No live webhook or deployment was used; GitHub cache behavior was modeled offline against the documented semantics. |
qiuethan
left a comment
There was a problem hiding this comment.
Requesting changes on cee9ab3be38361155bdb4e40133f0819e2a7d086 based on the independently verified findings.
Please fix the P2 recovery-state bug before merging: after 503 → 503 → 200 (recovery delivery fails) → 503, the checker delivers a stale “Service recovered” message while the endpoint is down, sets alertedDown=true, and suppresses subsequent outage alerts. Reconcile pending recovery notifications with the latest probe before retrying.
The same review also confirms three P3 issues: unbounded webhook delivery can block later probes and state saving; re-running a successful workflow can restore stale state through an immutable cache key; and the manual outage-test instructions omit the required polling-interval waits.
Claude independently reproduced the findings against this unchanged head. The committed tests pass, and the two previously resolved webhook findings remain fixed. Verification used offline fakes; live Actions cache behavior was modeled against GitHub's documented semantics. Detailed locations, reproductions, and suggested corrections are in the linked comment.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Fixed and pushed in 882e5c3.
Validation: |
qiuethan
left a comment
There was a problem hiding this comment.
Requesting changes on 882e5c3: two alert reliability issues remain in the revised recovery and timeout handling. The inline comments include reproductions and suggested corrections.
Verification: all 10 committed tests and both JavaScript syntax checks pass on Node 20. Both findings were reproduced against this head using offline fakes and a local HTTP server; no live webhook or deployment was used. git diff --check passes.
| if (previous.pendingNotification?.type === 'recovery') { | ||
| delete current.pendingNotification; | ||
| current.alertedDown = false; |
There was a problem hiding this comment.
[P2] Preserve the delivered outage state when discarding a stale recovery
With the default threshold, 503 → 503 → 200 (recovery webhook fails) → 503 → 200 delivers the initial down notification but never delivers recovery. On the fourth probe this branch deletes the pending recovery and sets alertedDown=false; the fifth and all subsequent healthy probes therefore have no reason to send a recovery. I reproduced this on Node 20, including another healthy check: the only delivered message remained the original outage, with no pending notification left.
Discard the superseded recovery message, but preserve the fact that a down alert was successfully delivered until a recovery is successfully delivered. Add regression coverage for a single failing probe after the failed recovery delivery, followed by sustained success.
| const delivery = sendNotification(alert.content); | ||
| const timedOut = new Promise((resolve) => { | ||
| timeout = setTimeout(() => resolve(false), timeoutMs); | ||
| }); | ||
| return (await Promise.race([delivery, timedOut])) !== false; |
There was a problem hiding this comment.
[P2] Abort the webhook POST when the delivery timeout expires
Promise.race() stops waiting without cancelling the underlying POST, which has no abort signal. I reproduced this with the actual CLI on Node 20 and a local server that accepts requests but never responds: with a 30 ms notification timeout, both services were checked and the state file was written, yet the process was still alive after one second with both webhook sockets open. The committed test uses an unresolved promise without an active socket, so it misses this behavior.
A prolonged stall can exhaust the workflow's five-minute job deadline before the separate cache-save step runs, losing updated debounce and pending-notification state. Pass an abort signal to the webhook fetch and abort the request on timeout; include a process-level test that verifies the CLI exits after a stalled delivery.
What this changes
Adds an external scheduled readiness checker that polls configured HTTPS readiness endpoints, debounces consecutive failures, and sends one down notification plus one recovery notification to a dedicated Discord operations webhook. Includes offline tests and the operational runbook requested by #55.
Why
Closes #55. Readiness failures from #54 need an external observer and human notification instead of relying only on Railway's internal health checks.
Zone
.github·scripts·docsThis is deliberately cross-zone because the implementation spans the scheduled GitHub Actions workflow, checker code, and its deployment/runbook documentation.
How to verify
Results: all 5 readiness tests passed; both JavaScript syntax checks passed;
git diff --checkpassed. The checker entrypoint was also smoke-tested with an empty endpoint configuration and correctly wrote an empty state file without network access.Checklist
stagingand targetingstaging(or this is a deliberatestaging → mainpromotion).uv run ruff check .anduv run ruff format --check .clean — CI gates both on every Python job.docs/CONTRIBUTING.mdpre-push checklist.API.mdfor a new endpoint, the README config table and.env.examplefor a new setting,ARCHITECTURE.mdfor a new trade-off,DEPLOYMENT.mdfor a new variable or scope.Deployment notes
READINESS_ENDPOINTSto a JSON object of externally reachable HTTPS readiness URLs; optionally setREADINESS_CHECKER_RUNNER,READINESS_POLL_INTERVAL_SECONDS, andREADINESS_FAILURE_THRESHOLD. Set the dedicatedDISCORD_READINESS_WEBHOOKActions secret before enabling the workflow.Anything you're unsure about
The monitored readiness URLs must be externally reachable or the workflow must use an approved self-hosted runner via
READINESS_CHECKER_RUNNER, because Railway services remain private by default.