Skip to content

1.9.79: LTV Settings reads the row a button names exactly - #296

Merged
tracking202 merged 13 commits into
1.9.79from
1.9.79-ltv-delete-webhook-id
Oct 28, 2026
Merged

tracking202 merged 13 commits into
1.9.79from
1.9.79-ltv-delete-webhook-id

Conversation

@tracking202

@tracking202 tracking202 commented Oct 28, 2026 •

Copy link
Copy Markdown
Owner

Stacked on #289 (8YRG, 1.9.79-ltv-webhook-revive, now at 7ac4aa9), which is stacked on #273. Review and merge this after #289. Until #289 merges, this diff includes #273's and #289's commits. This PR's own commits are:

5609f2a and 78afac8 are merges of #289's branch.

Backlog: 960Z

What was wrong

LTV Settings' Delete button posts webhook_id, and the page read it with (int) (CLAUDE.md #4). An id sent as 12abc therefore deleted webhook 12: one of the account's own webhooks, but not the one named. I measured this live against the page as #289 left it: webhook_id=5abc answered "Webhook deleted." and webhook 5 was gone.

The same page cast its other POST fields too:

field read as what that did
field_id, integration_id (Delete) (int) 3abc deleted field or integration 3 (measured live)
show_deliveries (Log) (int) 6abc showed webhook 6's delivery log (measured)
cparam (int) 1abc saved c1 (measured)
field_required !empty() false made the field required
webhook_events is_array ? strval… : [] a string, or no box ticked, became [], which create() reads as every event (measured: no box ticked registered a webhook)
every text field (string) a list (name[]=…) became "Array"; an integration's provider was stored as array

What changed

  • Prosper202\Http\PostedId::read() reads the id a form or a row's button posts, or returns null for the page to refuse in its own words. It accepts digits only, with no sign and no leading zero, and round-trips the value, so a value past PHP_INT_MAX is no id. (1.9.79: a dead LTV webhook can be revived, and is sent what it missed in the last 7 days #289's regex alone read it as PHP_INT_MAX.)
  • Delete and Revive read webhook_id through the same helper.
    • Delete refuses 12abc, 0, -1, an empty id and an absent one with "Pick the webhook to delete from the list."
    • Revive keeps its own sentence.
    • The field's and the integration's Delete do the same ("Pick the field / integration to delete from the list.").
    • So does the delivery log ("Pick the webhook whose log to show from the list."): no log is shown. No show_deliveries at all is still no log and no refusal.
  • The other casts on the page, swept the same way:
    • cparam must be exactly 0–4, the menu's values. Absent is refused, where it was read as Off.
    • The Required box is 1 or absent; anything else is refused.
    • webhook_events must be a non-empty list of strings.
    • Text fields go through ltv_settings_posted(), which refuses a list by name.
    • action sent as a list is "Unknown action." with no cast and no warning.
  • 1.9.79: a dead LTV webhook can be revived, and is sent what it missed in the last 7 days #289's revival can now answer a conflict (7ac4aa9): it waits up to 20 s for a dispatcher run still sending, then gives up with nothing changed. The page now says so in its own words: "Webhook #N was not revived: webhook deliveries are being sent right now. Nothing was changed; Revive it again in a minute." Before, it showed the repository's sentence, which is written for the API.
  • Machine check.
    • ForbidNumericCastOfRequestParamRule now reads the request superglobals ($_GET, $_POST, $_REQUEST, $_COOKIE) everywhere, not only in api/. Outside api/ its message names PostedId::read().
    • $params/$queryParams are still read only in api/, because elsewhere those names say nothing.
    • The 28 pages that still cast (66 sites) are listed in phpstan-baseline.neon, one entry per file by identifier and count. A page can lose a cast but cannot gain one.
    • CLAUDE.md subid missing in pixel url #4 says this, and also what the rule does not see: a value copied out of $_POST first, and (string).

Decisions to check

  • No event ticked is refused ("Tick the events this webhook is sent."). The page used to register the webhook for every known event, because it read nothing as [] and create() reads [] as all. The API's documented [] = every event is unchanged.
  • An absent cparam is refused rather than read as Off. The form's select always posts it (CLAUDE.md subid missing in pixel url #4: a form that posts every field rewrites every field).
  • The baseline grows by 28 entries (identifier + path + count). The other way to get the rule over the pages was a structural test with its own list; the baseline is where this repo already lists legacy findings. 1.9.79: the cron job sweeps lapsed LTV subscriptions hourly and runs the rest of LTV maintenance daily #285 removes the ltv_maintenance.php entries just below where 202-cronjobs/dej.php's entry goes, so expect a trivial adjacent-line conflict there.
  • (string) casts and the falsy test are fixed on this page by hand and tested. No rule was extended to cover them tree-wide, because the falsy test alone has far more legacy sites.

Tests

  • tests/Ltv/LtvSettingsPostedValuesIntegrationTest.php (@group integration, scratch database, 9 tests). It runs the real page as the account through tests/Setup/fixtures/page-runner.php, the way the web server runs it, and posts every spelling.
    • For each id it posts Nabc, 0, -1, '', N, N , N.0, 0N, +N, [N] and absent. Each must answer the page's sentence and leave the record.
    • It also covers the c-param, the Required box, the events, a text field sent as a list and the action sent as a list. The page may print no warning.
    • It holds the dispatcher's lock while the page revives a dead webhook. The test waits the 20 s, then expects the page's sentence, with the webhook still dead.
  • tests/Http/PostedIdTest.php.
  • NumericCastOfRequestParamRuleTest. The fixture's file outside api/ now plants four superglobal casts, which must be reported, beside $params and (string), which must not.
  • Plants. 16 plants, all caught. For each, the file was backed up to mktemp, compared with cmp and restored. A plant counted as caught only when a named test failed (for the two baseline rows, the phpstan tier). The harness made 18 runs, and two first runs missed. Neither removed a fix:
    • the first field_required plant changed the payload line while the match still refused. It was planted again with the match removed, and was caught.
    • removing PostedId's /D alone is caught by nothing, because its round trip refuses "12\n" anyway. Removing the round trip is caught.
The plants
plant caught by
Delete reads (int) $_POST['webhook_id'] testDeleteReadsTheWebhookIdExactly
Revive reads (int) testReviveReadsTheWebhookIdAsDeleteDoes
field Delete (int) testAFieldAndAnIntegrationAreDeletedOnlyByTheirExactIds
integration Delete (int) same
show_deliveries (int) testTheDeliveryLogIsShownOnlyForTheWebhookNamed
c-param checked by (int) range testTheCustomerCparamIsOneOfTheMenusValues
Required read with !empty() testAFieldIsRequiredOnlyWhenItsBoxIsTicked
events: non-list folded to [] testAWebhookIsSentTheEventsTickedAndNoneTickedIsRefused
events: element not checked same
text read with (string) testATextFieldSentAsAListIsRefusedNotStoredAsArray
action read with (string) same (the page's warning)
PostedId without its round trip PostedIdTest::testAnythingElseIsNoId
the rule back to api/ only NumericCastOfRequestParamRuleTest::testEveryPlantedShapeIsReportedOnceAndNothingElseIs
the rule reads $params outside api/ same
phpstan, the Delete cast put back phpstan tier: $_POST['webhook_id'] is read with (int) in ltv_settings.php
phpstan, one more cast in a listed page (ltv_companies.php, 7 → 8) phpstan tier

The revival-conflict sentence was added after the plants ran. Its test fails without the catch, because the page then shows the repository's words.

Live pass

tests/live/ltv-settings-posted-ids.sh (new) ran on an instance installed from this branch: PHP 8.3.6 php -S through the eval router, MariaDB 10.11, port 8321. 27 passed, 0 failed, on 44591e2 and again on ba57261 after the merge. The same script against the page as #289 had it gave 10 passed and 17 failed; 5abc deleted webhook 5.

  1. The pass signs in through 202-login.php's own form (form-body.py), then makes two webhooks, a field and an integration through the API.
  2. Delete is posted as its script posts it (ltvSettingsAction: the action, the page's token, the id) with Nabc, 0, -1, empty and absent. Each is refused with "Pick the webhook to delete from the list.", and both webhooks are still listed by GET /ltv/webhooks. Then N deletes that webhook and only that one.
  3. Revive, with the webhook marked dead in the database: Nabc is refused and the webhook stays dead; N makes it active. The field's and the integration's Delete refuse Nabc and keep the row, then N deletes it.
  4. The delivery log: Nabc is refused and no log is shown; N shows webhook N's log.
  5. The forms, submitted with their own fields:
    • the prefs form with cparam=1abc is refused, and the stored c-param is unchanged;
    • the webhook form with every box unticked is refused, and no webhook is made.

The revival conflict was not driven live: it needs a dispatcher run held in flight. The integration test drives it through the real page.

Licence (the stub is down)

  • Install. install.php's POST checks the key with my.tracking202.com, so I did not run it. A scratch script ran the installer's own steps without the licence form: INSTALL::install_databases(), InstallAccount::create(), the 202_api_keys row and install_database_partitions(). AutoCron and the daily email registration, which call outside services, were left out.
  • Signed-in pages. The web server ran with every proxy variable (HTTPS_PROXY, https_proxy, HTTP_PROXY, http_proxy) pointed at http://127.0.0.1:9, a closed port, and with NO_PROXY=localhost,127.0.0.1, so there was no bypass for my.tracking202.com. The licence check then gets no answer, and AUTH::is_valid_api_key() fails open, as it does on any network failure. Every licence check in the server log is Failed to connect to 127.0.0.1 port 9. Nothing reached the licence service, and no code or session was changed for the pass.
  • The Go CLI was not used. No CLI command changed here.
  • The buttons were not clicked in a browser. What ran are the POSTs their script and the forms make.

Verification

.claude/skills/p202-verify/scripts/verify.sh --since origin/1.9.79 --changed, on ba57261 (after the merge of 7ac4aa9), PHP 8.3.6. The schema tier ran on a scratch MariaDB 10.11 database. The unit tier ran 6,118 tests with 5 skipped: the 4 documented "User class requires DB singleton" skips, and CronEntryPointsFailLoudlyTest's executed case, which skips while the worktree holds the live instance's 202-config.php. This range includes #273's and #289's changes, which is why the Go and openapi tiers ran.

Scope report
------------
  go         PASS  ran under CI's go1.25.14 via GOTOOLCHAIN (local go is 1.24)
  golangci   PASS  ran under CI's go1.25.14 via GOTOOLCHAIN (local go is 1.24)
  openapi    PASS
  patterns   PASS
  phpcs      PASS
  phpstan    PASS
  schema     PASS
  syntax     PASS
  unit       PASS

Not covered by this script:
  live      stand up an instance, drive the path a user would take
  agent     add and run a case under tests/fixtures/agent-eval/cases/
  ci        a workflow still in_progress has not passed; read the run

Report the lines above verbatim. Do not write "tests pass".

Beyond the script, after the merge, each suite against a scratch MariaDB 10.11 database:

suite result
LtvSettingsPostedValuesIntegrationTest 9 tests OK
WebhookReviveIntegrationTest 11 tests OK
WebhookDispatchIntegrationTest 15 tests OK

shellcheck --severity=warning and bash -n on the new live script: clean.

Not run:

  • a browser pass (the buttons' POSTs ran; see above);
  • an agent-eval case (no agent-facing surface changed);
  • CI's MySQL 8.0 (only MariaDB 10.11 here). The new test's SQL is INSERT … SET, DELETE, UPDATE and GET_LOCK, all valid on MySQL 8;
  • the full tests/run-integration-suites.sh;
  • CI, until it runs on this PR.

Found outside this item (not fixed here)

  • The other LTV pages cast posted ids the same way, and are now listed in the baseline:
    • ltv_companies.php (7 sites): merge reads source_company_id and company_id with (int), so 12abc merges company 12;
    • ltv_products.php (5);
    • ltv_customer.php (3): alias_id and source_customer_id;
    • ltv_subscriptions.php, ltv_merge_search.php and sort_ltv.php (1 each).
  • LTV Settings asks no role permission (no AUTH::require_permissions): any signed-in role can delete or revive the account's webhooks, fields and integrations.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr

LTV webhooks were queued by every /ltv write and sent by nothing a
default install runs: 202-cronjobs/ltv_webhooks.php was the only
dispatcher, and every default scheduler (the installer's cron line,
install.sh, both Docker cron services, AutoCron) fetches index.php
alone. EveryCronJobIsScheduledByDefaultTest listed it as NOT_SCHEDULED
("opt-in, predates the measurement rewrite": history, not a reason). The
Landing Page Optimizer's pairing webhook waited with the rest.

The dispatch moves into Prosper202\Ltv\WebhookDispatcher, and two entry
points run it: the minutely tier of index.php (last, with a 15-second
budget) and ltv_webhooks.php on its own (--budget=N, default 50), which
the Coolify worker service now runs too. Both take one MySQL named
lock, as the attribution worker and the Android intake do, and each
delivery is still claimed by compare-and-set before it is sent, so runs
the lock does not see cannot send it twice either.

- The deadline is asked before each delivery, at least one a run, so a
  run ends at most one delivery past it.
- A webhook whose delivery took 2 s or more to fail is not sent to again
  in that run; its other deliveries stay due, untried, and a cursor on
  (next_attempt_at, delivery_id) steps past them. A receiver that times
  out costs one timeout a run instead of the budget every webhook on the
  install shares. A failure that comes back at once is retried at its own
  pace, as before.
- Retries and backoff are unchanged (recordAttempt(): 2^n minutes, failed
  after 6, the webhook marked dead). A refusal, a failed lookup or a send
  that throws fails its own delivery and the run goes on (#24).
- A delivery still unsent 7 days after it was queued is failed with
  "expired: ..." as its last response, never sent, its webhook left
  active: an upgraded install sends the last week of its backlog, oldest
  first, rather than months of stale events. Expired 1,000 a statement;
  a run whose budget ends first sends nothing, since what is left to
  expire is the oldest due.
- The response body is stored as UTF-8 cut at a character boundary: a
  body that is not UTF-8 was refused under a strict sql_mode, the outcome
  went unrecorded and the delivery would have been sent again.
- post() builds every option through requestOptions(), the guard's set
  winning over the POST's, and refuses to send when curl refuses one.

Tests: WebhookDispatchIntegrationTest (a real server, a php -S receiver,
two child processes for the overlapping runs, under the lock and
without it), WebhookDispatcherTest, LtvWebhooksAreSentEveryMinuteTest
(the minutely tier, last, with its budget; the Coolify worker), and the
scheduling, guard-parity and LTV server-failure tests updated. Each
removal planted was caught by a named test. tests/live/ltv-webhooks.sh
is the pass end to end against an https receiver.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
1.9.79 gained #262. The only conflict was 202-config/changelogs.json, where
both sides added 1.9.79 lines: 1.9.79's lines are kept and this branch's
follow them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
Brings in what 1.9.79 gained since this branch last merged it. The
conflicts were only in the changelogs, where both sides added 1.9.79
lines: 1.9.79's lines are kept as it has them and this branch's follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
… in the last 7 days

A webhook is marked dead when one delivery fails 6 times, about an hour
of retries now that #273 sends them, and nothing could make it active
again: LTV Settings said to register it again, which for the Landing Page
Optimizer, whose pairing keeps the webhook's id and secret, meant pairing
again. A dead webhook was also queued nothing, and the deliveries it had
pending spent their remaining attempts on "endpoint not active", so the
events of an outage were lost for good.

- MysqlWebhookRepository::revive() makes a webhook that is not active
  active again with its id, URL and secret, resets its pending
  deliveries' attempts to 0, and (requeue, the default) makes every
  delivery of it not delivered and queued within the dispatcher's 7 days
  pending again, due now: the one that killed it and the ones held while
  it was dead. A delivered one is never touched. It locks the webhook row
  first, and an active webhook is left as it is.
- While dead, enqueue() still queues its events, and the dispatcher holds
  each (holdDelivery(): failed, attempts as they were, "held: …"), never
  trying it. The hold asks the webhook's status again in its own UPDATE,
  so a delivery read while the webhook was dead and revived since is sent.
- PUT /ltv/webhooks/{id} {"status": "active", "requeue": bool}, LTV
  Settings' Revive button on a dead row (its confirm names how many it
  missed), p202 ltv webhooks revive <id> [--requeue=false] and bin/p202
  ltv:webhook:revive <id> [--no-requeue].

requeue is the default: the receiver was down, the events it missed are
what keeps a subscriber (the optimizer's conversions, a CRM's revenue)
consistent, the 7 days are the window the dispatcher already applies, and
every one goes with its own X-P202-Delivery, so a receiver that did take
one de-duplicates it. requeue false is there for a receiver that was
replaced and wants new events only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
…webhook-id

This branch is stacked on #289: its Revive action reads the webhook id
strictly, and the fix here reads Delete's id the same way. No conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
LTV Settings' Delete read webhook_id with (int), so an id sent as 12abc
deleted webhook 12: one of the account's own, and not the one named
(CLAUDE.md #4). Measured live against the page before this change:
webhook_id=5abc answered "Webhook deleted." and webhook 5 was gone.

Delete now reads the id as #289's Revive does, through one helper both
use (Prosper202\Http\PostedId::read(): digits only, no sign, no leading
zero, round-tripped so a value past PHP_INT_MAX is no id), and refuses
anything else with "Pick the webhook to delete from the list.". The
page's other casts are swept the same way: the field's and the
integration's Delete, the delivery log's webhook id, the customer
c-param (1abc saved c1), the Required box (!empty() read "false" as
ticked), the webhook events (a string, or no box ticked, subscribed the
webhook to every event), and every text field, which (string) read as
"Array" when sent as a list (an integration's provider was stored as
"array").

ForbidNumericCastOfRequestParamRule now reads the request superglobals
outside api/ as well, naming PostedId::read() there; the 28 pages that
still cast (66 sites) are listed by count in phpstan-baseline.neon, so
the counts only shrink. CLAUDE.md #4 says so, and what the rule does not
see.

Tests: PostedIdTest; LtvSettingsPostedValuesIntegrationTest runs the page
as the account (page-runner) and posts each spelling; the rule's fixture
now plants superglobal casts outside api/. tests/live/
ltv-settings-posted-ids.sh is the live pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
Copilot AI balanced review requested due to automatic review settings October 28, 2026 08:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-28T12:39:36.600424Z
We couldn’t complete this review. Comment @codex review to try again.
c4df12d New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

Copy link
Copy Markdown
Contributor

Code review: CLEAN

For 44591e2: 0 issue(s). The review is encrypted for the maintainers in the review-findings artifact of this run.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 44591e2168

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread 202-config/Ltv/MysqlWebhookRepository.php Outdated
@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Adversarial review: NON-BLOCKING

For c4df12d: 6 finding(s). The report is encrypted for the maintainers in the review-findings artifact of this run.

Rewritten on every push; the check fails on BLOCKING.

… and waits for a run that is sending

revive() with requeue false only reset the pending deliveries' attempts
and made the webhook active, so the next run sent every delivery queued
while it was dead that no run had held yet, and every one waiting out a
retry's backoff: "new events only" sent old ones (Codex, on #296).

Without requeue, every delivery of the webhook still pending is now held
in the revival's transaction, as a dead webhook's are: failed, its
attempts as they were, "held: its webhook was revived for new events
only, …", never sent. The answer says how many (held).

With requeue, the pending ones are sent: their attempts go back to 0
and each keeps its due time, so one waiting out a backoff waits at most
32 minutes. A claim (claimDelivery()) reads the same as a backoff, so a
due time is never shortened. The failed ones it sends again are due at
their queueing rather than at the revival, so they go oldest first with
the pending ones no run has held yet.

A delivery a run has claimed and not yet recorded must not be held or
made due under it: a hold would be overwritten by the run's record (a
held delivery sent anyway), and a requeued one could be claimed again
while the first send is in flight. revive() now takes the dispatcher's
lock (WebhookDispatcher::betweenRuns()), waiting up to 20 seconds for a
run in flight; a run still sending then is an LtvConflictException, a
409 through the API with nothing changed, and the Go CLI hints to run
it again in a minute rather than "update it instead".

Tests: WebhookReviveIntegrationTest gains a pending row at revival time
both ways and a revival from a second session while a run has claimed a
delivery; each failed against a78e59a. The live pass revives with a
delivery still pending, both ways.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
…v-delete-webhook-id

Brings in #289's fix for a revival without requeue, which takes the
dispatcher's lock and can answer a conflict. The conflicts were only in
changelogs.txt and tests/live/README.md: #289's revised revive lines are
kept as it has them, and this branch's lines follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
#289's revive() now waits up to 20 seconds for a dispatcher run still
sending and then throws LtvConflictException with nothing changed. The
page showed the repository's API-worded sentence; it now says, in its own
words, that webhook #N was not revived because deliveries are being sent
right now, nothing was changed, and to revive it again in a minute.

LtvSettingsPostedValuesIntegrationTest holds the dispatcher's lock while
the page revives a dead webhook: the sentence, and the webhook still dead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Brings in 1.9.79's new commits. The conflicts were in 202-config/changelogs.json, changelogs.txt, tests/live/README.md, where both sides added lines: the incoming lines are kept as they are and this branch's follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
Brings in ltv-webhooks-scheduled (this branch's stacked base), and through it 1.9.79's new commits. The conflicts were in 202-config/changelogs.json, where both sides added lines: the incoming lines are kept as they are and this branch's follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
Brings in ltv-webhook-revive (this branch's stacked base), and through it 1.9.79's new commits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0ad7dfcbc6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread 202-config/Ltv/MysqlWebhookRepository.php Outdated
missedCounts() counted the failed deliveries a revival requeues and not
the pending ones it leaves to be sent: those queued after the webhook
died but before the next run held them, and retries waiting out their
backoff, whose attempts revive() resets (found by review). The Settings
confirmation could then promise fewer deliveries than the revival sends.
It counts both, queued within the dispatcher's 7 days, the same set
revive() sends; the integration test reads the count between the queueing
and the run, and fails with the old count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@tracking202
tracking202 merged commit 8764b90 into 1.9.79 Oct 28, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants