You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[P3][bug] Orphaned live-contract watch still active on the shared test key since 2026-09-04; smoke cleanup is skipped whenever id extraction fails #126
Confirmed live against https://api.oilpriceapi.com on 2026-09-13 with the shared test key (read-only GET /v1/subscriptions), and in code on origin/main.
What is live
GET /v1/subscriptions on the shared test key returns one watch nobody owns:
It has been snapshotted hourly for nine days (a WatchEvent per interval via WatchSnapshotWorker). From the API code, watch evaluation does not consume request quota, so the cost is event rows and noise in any events poll on this key, and it occupies one watch slot on whatever tier the key is on.
Why it leaked (from git history)
36e8261 (Cover every MCP tool with a generated live contract matrix #80, merged 2026-09-04) added subscriptionLifecycle() in scripts/live-smoke.mjs reading id = created.body?.subscription?.id. Production nests the record under data, so id was undefined, the assert(id, ...) threw, and the finally guard if (id) await deleteAndVerify(...) skipped cleanup for a watch the API had created.
b81f8e9 (Reconcile MCP handlers with live production envelopes #82) changed the read to created.body?.subscription?.id ?? created.body?.data?.subscription?.id at 08:26 -04:00 (12:26Z), four minutes after this watch was created (12:22:19Z).
The id read is fixed. The structural flaw is not: cleanup still depends on parsing the create response. If the envelope moves again, or the assertion after create fails before id is set, the next run leaks another watch the same way.
Suggested fix
Delete b84b24a0-2b28-4eac-835e-db92bab5c0cb from the test account (not done here: it is not a resource this session created).
In finally, when id is unset but create returned 2xx, fall back to listing /v1/subscriptions and deleting records whose name equals the generated mcp-live-contract-<ts>. Same for the price-alert lifecycle.
Optionally sweep mcp-live-contract-* records older than an hour at the start of each run, so any past leak self-heals.
Confirmed live against
https://api.oilpriceapi.comon 2026-09-13 with the shared test key (read-onlyGET /v1/subscriptions), and in code onorigin/main.What is live
GET /v1/subscriptionson the shared test key returns one watch nobody owns:{"id":"b84b24a0-2b28-4eac-835e-db92bab5c0cb","name":"mcp-live-contract-1788524538194","codes":["BRENT_CRUDE_USD"],"interval_seconds":3600,"status":"active","source":"api","tool_name":"opa_create_price_subscription","last_evaluated_at":"2026-09-13T19:32:06Z","next_run_at":"2026-09-13T20:32:06Z","created_at":"2026-09-04T12:22:19Z"}It has been snapshotted hourly for nine days (a
WatchEventper interval viaWatchSnapshotWorker). From the API code, watch evaluation does not consume request quota, so the cost is event rows and noise in any events poll on this key, and it occupies one watch slot on whatever tier the key is on.Why it leaked (from git history)
36e8261(Cover every MCP tool with a generated live contract matrix #80, merged 2026-09-04) addedsubscriptionLifecycle()inscripts/live-smoke.mjsreadingid = created.body?.subscription?.id. Production nests the record underdata, soidwasundefined, theassert(id, ...)threw, and thefinallyguardif (id) await deleteAndVerify(...)skipped cleanup for a watch the API had created.b81f8e9(Reconcile MCP handlers with live production envelopes #82) changed the read tocreated.body?.subscription?.id ?? created.body?.data?.subscription?.idat 08:26 -04:00 (12:26Z), four minutes after this watch was created (12:22:19Z).The id read is fixed. The structural flaw is not: cleanup still depends on parsing the create response. If the envelope moves again, or the assertion after create fails before
idis set, the next run leaks another watch the same way.Suggested fix
b84b24a0-2b28-4eac-835e-db92bab5c0cbfrom the test account (not done here: it is not a resource this session created).finally, whenidis unset but create returned 2xx, fall back to listing/v1/subscriptionsand deleting records whosenameequals the generatedmcp-live-contract-<ts>. Same for the price-alert lifecycle.mcp-live-contract-*records older than an hour at the start of each run, so any past leak self-heals.Found while writing the Node SDK subscriptions lifecycle smoke (OilpriceAPI/oilpriceapi-node#78).