fix: preserve human steering during Slack degradation#95
Conversation
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
Warning Review limit reached
Next review available in: 5 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (14)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces a webhookHealthy field to the ProviderSyncStatus interface and parses it from the sync status payload (supporting both camelCase and snake_case). This field is used to prevent soft degradation of Slack sync (e.g., when the sync watermark is stale but webhooks are actually healthy), ensuring that Slack writeback is not unnecessarily skipped. Comprehensive unit tests have been added to verify this behavior. I have no feedback to provide as there are no review comments.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f424c28f78
ℹ️ 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".
f424c28 to
7bc49c5
Compare
khaliqgant
left a comment
There was a problem hiding this comment.
Heads-up on branch ownership: we collided — I had pushed a narrower fix here (f424c28) and this branch was force-pushed over it. No complaint, your version is better and I'm not pushing over it. Two findings from reviewing it against my copy, one of them a proven bug.
Your veto is better than mine — keep it
I put the veto inside slackSyncStatusResult; you put it in #slackFreshness() with slackGateBypassedByWebhookHealth + an info log. Yours is observable where mine was silent, and the soft-only/hard-still-degrades semantics match. Yours also does the thing I could only suggest in #94: decoupling the ask instructions from thread existence and routing Slack → GitHub → operator-visible error, so a degraded Slack no longer silently strips the agent's ability to ask. That's the deeper fix.
Proven bug: the hasProviderSyncFreshness change drops real freshness
Adding webhookHealthy to hasProviderSyncFreshness changes source selection. A wrapper carrying only webhook health now qualifies as the freshness-bearing candidate and wins candidates.find(hasProviderSyncFreshness), so the actual watermark on a nested connection is discarded.
Repro against this branch:
fake.getSyncStatus = async () => ({
webhookHealthy: false, // wrapper: health only, no freshness
connections: [{ provider: 'slack', lastEventAt: '2026-06-06T12:05:00.000Z' }], // real, stale watermark
})
await mount.getSyncStatus('slack')
// RESULT: {"provider":"slack","webhookHealthy":false} <-- lastEventAt GONEConsequence: a genuinely dead Slack (webhookHealthy: false + stale watermark) normalizes to no lastEventAt, no lagSeconds, no status → slackSyncStatusResult returns { known: false, degraded: false } → not degraded. Factory then keeps writeback pointed at a Slack nobody reads: the question posts, no reply ever arrives, and the agent waits. That's the inverse of the failure this PR fixes, and it's the case the gate exists for.
Also still live: the Codex P2 (source-only read)
webhookHealthy: booleanField(source, ...) at relayfile-cloud-mount-client.ts:504 still reads only from source. The hasProviderSyncFreshness change masks Codex's specific example (the wrapper now wins), but by dropping the watermark rather than reading both — so the flag and the freshness can still disagree about which object they came from.
Suggested fix — one change addresses both
Revert the hasProviderSyncFreshness addition (let freshness selection stay freshness-based), and read the flag across candidates instead:
// `source` is picked for *freshness*, so webhook health can sit on a different candidate.
// An explicit `false` from any candidate wins: a wrong `true` keeps writeback pointed at a
// Slack nobody reads, which is the worse failure.
const webhookHealthySignals = [source, ...candidates]
.map((c) => booleanField(c, 'webhookHealthy') ?? booleanField(c, 'webhook_healthy'))
.filter((v): v is boolean => v !== undefined)
// ...
webhookHealthy: webhookHealthySignals.includes(false) ? false : webhookHealthySignals[0],This keeps the stale watermark and sees the wrapper's flag, so the two can't disagree. Verified RED-before-green on my copy for both Codex's shape and the drop-freshness shape above.
Happy to push these two onto this branch if you'd rather not context-switch — say the word and I'll rebase onto your head rather than over it.
Field context, if useful
This is what a live Slack reports today (AgentWorkforce/cloud#2666): lastEventAt only advances on sync events and the schedule is unregistered, so it reads 6wk stale while webhooks deliver in ~8s (verified with a live post). webhookHealthy: true was correct throughout — hence this PR. Reproduced end-to-end on published 0.1.19 before the fix: relayed#48 (deliberately unanswerable from the repo) → agent never told it could ask → 30min stall, zero commits, no PR (timed out waiting for spawned agents to exit). Details in #94.
|
@codex review |
|
To use Codex here, create a Codex account and connect to github. |
|
@khaliqgant Addressed the proven split-response bug from review 4722147263 in |
Fixes #94.
Summary
[factory-needs-input]instructions for normal implementers, reviewers, and issue-driven babysitters whether or not a Slack dispatch thread exists.webhookHealthyas an independent signal across split wrapper/nested response shapes while preserving the nested sync watermark; any explicit unhealthy signal wins. Healthy webhook delivery and actual observed event receipt may bypass only soft sync degradation; expliciterror/failedstatus and confirmed write failures remain hard failures.Existing PR #95 containment
The original five-file
webhookHealthypatch atf424c28is fully and semantically contained in this signed candidate. The existing PR branch was updated with an explicit force-with-lease from that exact head to the audited commit; no content change beyond the signed V4 patch was required.Validation
tsc -p tsconfig.build.json --noEmitpassed.npm run buildpassed.npm pack --dry-run --json --ignore-scriptspassed with 266 files.git diff --checkpassed.Independent audit
475605ded5985a8efede26fa6efc2f441d3fb6fa321c623be13beeeb3ed94e887d1df4a499e81ad357f3e5663abda618919db46b2f039199c08802419d9d3c0401da2e4081f9bcf8This PR is intentionally not merged by the implementation agent.