Fix Instatus fetcher schema rejects numeric maintenance duration (bluesky) - #2702
Open
polylane[bot] wants to merge 1 commit into
Open
Fix Instatus fetcher schema rejects numeric maintenance duration (bluesky)#2702polylane[bot] wants to merge 1 commit into
polylane[bot] wants to merge 1 commit into
Conversation
vercel
Bot
temporarily deployed
to
Preview – openstatus-status-page
September 10, 2026 19:38
Inactive
|
The latest updates on your projects. Learn more about Vercel for GitHub. 3 Skipped Deployments
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes: Instatus fetcher schema rejects numeric maintenance duration (bluesky)
The external-status cron failed on every tick while Bluesky's status page was under maintenance: Instatus serves
activeMaintenances[].durationas a JSON number, and the instatus fetcher's schema only accepted a string, so each fetch threw a schema-mismatch FetchError to Sentry and Bluesky's entry in the public external-services directory kept its stale pre-maintenance status. This widens the field to accept both shapes, so the fetcher records the maintenance instead of failing, and any other Instatus-hosted page stops hitting the same wall during its next maintenance window.What changed
packages/status-fetcher/src/fetchers/instatus.ts:instatusMaintenanceSchema.durationwidened fromz.string().optional()toz.union([z.number(), z.string()]).optional(), matching the union idiom already used incustom.ts.packages/status-fetcher/__tests__/fetchers/instatus.test.ts: regression test replaying the exact live status.bsky.app summary.json payload (numericduration: 60, UNDERMAINTENANCE, INPROGRESS maintenance) and asserting it maps to under_maintenance with the maintenance's name and start time.Why it's safe
durationis never consumed downstream: the fetcher's normalize() maps only page.status, the maintenance's name and start, and activeIncidents; the workflows cron and detect.ts read no other maintenance fields, so accepting a wider type cannot change any mapped output.Validation
bun x deno check --sloppy-imports .in packages/status-fetcher (the package's declaredcheckscript): passbun x deno test --parallel -A --no-check --sloppy-importsin packages/status-fetcher (the declaredtestscript): 19 files passed, 243 steps, 0 failed — includes the new regression test replaying the live Bluesky payloadbun x deno check --sloppy-imports src/serve.tsin apps/workflows (its declaredcheckscript, covering the consumer): passoxfmt --checkrepo-wide: all files correctly formattedoxlinton packages/status-fetcher: 0 warnings, 0 errorsnode scripts/check-doc-refs.mts: no dangling referencesturbo run checkwrapper — pnpm is not installed in this sandbox, so each package's declared check script was run directly instead (results above)deno testsuite — requires a live DATABASE_URL; the change does not touch workflows code and its type gate covers the import surfaceRoot cause and scoping notes
Root cause
FetchError: [instatus (bluesky)] schema mismatch: https://status.bsky.app/summary.json, 2 events (19:00:31Z, 19:10:23Z) from the external-status cron on openstatus-workflows.activeMaintenances[0].durationis the number 60. The schema declaredduration: z.string().optional(); reproduced with zod 4.1.13 (the lockfile version): parse fails withinvalid_type, expected string, received numberat['activeMaintenances', 0, 'duration'], whichfetchJsonrethrows as the FetchError of kind=schema.durationis never read downstream, so mapped output for every previously-parsing payload is identical.detect.tsreuses the same exported schema, so the detection probe also stops misclassifying live Instatus pages during maintenances.Out of scope / follow-ups
Causal chain
Detection outcome
The fingerprinted signal
FetchError: [instatus (bluesky)] schema mismatch: https://status.bsky.app/summary.jsonstops firing once this deploys. On the cron's next tick, summary.json'sactiveMaintenances[0].duration(a JSON number while the maintenance is live) now parses against the widened union, so the fetch completes: the entry records UNDERMAINTENANCE / under_maintenance with the maintenance's name and start time instead of throwing and keeping a stale snapshot. The same holds for any other Instatus-hosted page that runs a maintenance: the schema shared by detect.ts and the fetcher no longer rejects the payload shape Instatus actually serves.Fix chosen
Chosen: cause (removes the mechanism that produces the failure): Widen instatusMaintenanceSchema's duration field from z.string() to z.union([z.number(), z.string()]), so the payload Instatus actually serves parses; this removes the mechanism that produces the schema-mismatch FetchError.
Considered and not chosen:
2 files changed (+32/-1)
packages/status-fetcher/__tests__/fetchers/instatus.test.ts: modified, +30/-0packages/status-fetcher/src/fetchers/instatus.ts: modified, +2/-1Repository conventions present in the repository: AGENTS.md, CLAUDE.md, apps/checker/AGENTS.md, apps/dashboard/AGENTS.md, apps/server/AGENTS.md, apps/status-page/AGENTS.md, apps/web/AGENTS.md, apps/workflows/AGENTS.md, packages/services/AGENTS.md, packages/ui/AGENTS.md.
Repository lint:
pnpm run lint(declared in package.json) could not run in the sandbox because its tool is not installed there; run it before merging.Generated by Polylane.