Skip to content

fix(linear-slack): stop the receipt-wait from eating replies; add a fast 👀 ack#55

Merged
khaliqgant merged 1 commit into
mainfrom
fix/linear-slack-receipt-timeout-and-ack
Jun 9, 2026
Merged

fix(linear-slack): stop the receipt-wait from eating replies; add a fast 👀 ack#55
khaliqgant merged 1 commit into
mainfrom
fix/linear-slack-receipt-timeout-and-ack

Conversation

@khaliqgant

@khaliqgant khaliqgant commented Jun 9, 2026

Copy link
Copy Markdown
Member

What the live trace showed

After #53/#54 deployed, a real run still posted nothing — but the issue was created (AR-87 in Launch SDK). The harness side is perfect now (resolved team/project ids, emitted a clean linear-actions block). The failure was entirely in the handler's writeback handling:

  1. Receipt timeout is 3s by default — too short for the cloud worker's round-trip. slack.post() returned ts:'' and createIssue() returned the draft-path fallback even though both writes landed (AR-87 materialized ~4 min later via the 30s mirror sync).
  2. fix(linear-slack): deliver Slack replies + guard the writeback-scope footgun #53's "throw on empty ts" backfired. With the /slack/channels scope mounted, the draft flushes at box cleanup — so an empty ts is a slow receipt, not a drop. But the throw crashed the turn and tore the box down before the reply flushed. Issue created, channel silent.

Fixes

  • Longer receipt window — build the slack + linear clients with writebackTimeoutMs: 12s, so receipts have time to arrive (and the box stays alive to flush) and we get real confirmation (a linear.app url / a real ts).
  • postReply no longer throws on a missing receipt — it logs linear-slack.reply.no-receipt and lets the draft flush. The genuine "nothing mounts /slack" failure is caught at build time by tests/persona-integration-scopes, so it doesn't need a turn-crashing runtime throw.
  • Honest, non-scary pending message — an unconfirmed create/comment now says "submitting now — it'll appear on the board shortly" (creates land via the mirror within ~minutes) instead of "never confirmed, double-check." Still logged.
  • Fast 👀 ack — react on the teammate's message the instant the handler picks up the turn (fire-and-forget; doesn't block the harness), so the channel gets acknowledgement within seconds of box-boot instead of ~3 min of silence.

Test

npm test 60/60, typecheck clean. Updated the unconfirmed-create test for the new message.

Follow-up to #53/#54. Framework-level receipt reliability tracked in AgentWorkforce/cloud#2029.

🤖 Generated with Claude Code


Summary by cubic

Stops linear-slack from dropping replies by waiting longer for writeback receipts and not throwing on slow receipts. Adds a fast 👀 reaction so users see an immediate ack.

  • Bug Fixes

    • Build Slack and Linear clients with writebackTimeoutMs: 12_000 to allow receipts to arrive.
    • postReply no longer throws on empty ts; logs linear-slack.reply.no-receipt and lets the draft flush.
    • Unconfirmed creates/comments are shown as “submitting now — appears shortly,” logged as linear-slack.action.unconfirmed.
  • New Features

    • Instant 👀 reaction on the user’s message when handling starts (fire-and-forget) to acknowledge the request.

Written for commit 7a5fac9. Summary will update on new commits.

Review in cubic

…ast ack

Live trace of a real run: the issue WAS created (AR-87 in Launch SDK) but no
reply ever posted. Two causes, one of them self-inflicted by #53:

- The writeback receipt timeout defaults to 3s — too short for the cloud
  worker's round-trip. `slack.post()` returned `ts:''` and `createIssue()`
  returned the draft-path fallback even though both writes landed.
- #53 made `postReply` THROW on an empty `ts`. With the scope mounted the draft
  still flushes at cleanup, so an empty `ts` is a slow receipt, not a drop — but
  the throw crashed the turn and tore the box down before the reply flushed.
  Issue created, channel silent.

Fixes:
- Build slack/linear clients with `writebackTimeoutMs: 12s` so receipts have
  time to arrive (keeps the box alive to flush) and we get real confirmation.
- `postReply` no longer throws on a missing receipt — it logs
  `linear-slack.reply.no-receipt` and lets the draft flush. The genuine
  no-scope failure is already caught at build time by
  tests/persona-integration-scopes, not at runtime.
- Reframe an unconfirmed create/comment from a scary "never confirmed" to
  "submitting now — appears on the board shortly" (creates land via the mirror
  within ~minutes), still logged for triage.
- Add a fast 👀 ack: react on the teammate's message the moment the handler
  picks up the turn (fire-and-forget, doesn't block the harness), so the channel
  isn't silent for the minutes-long run.

Follow-up to #53/#54. Refs AgentWorkforce/cloud#2029.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@codeant-ai

codeant-ai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Your free trial PR review limit of 300 PRs has been reached. Please upgrade your plan to continue using CodeAnt AI.

@khaliqgant khaliqgant merged commit c9f05d9 into main Jun 9, 2026
@khaliqgant khaliqgant deleted the fix/linear-slack-receipt-timeout-and-ack branch June 9, 2026 14:02

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces an immediate reaction acknowledgement ('eyes' emoji) to Slack messages, increases the writeback timeout to 12 seconds to accommodate cloud worker round-trips, and refactors error handling so that missing writeback receipts result in warnings rather than thrown errors or critical failures. Feedback on these changes suggests wrapping the fire-and-forget reaction in Promise.resolve().then(...) to prevent synchronous exceptions from crashing the handler, and restoring a try/catch block around the fallback postReply call in the error path to avoid masking the original harness error.

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.

Comment thread linear-slack/agent.ts
Comment on lines +163 to +165
void Promise.resolve(slack.react(msg.channel, msg.ts, ACK_EMOJI)).catch((err) =>
ctx.log?.('warn', 'linear-slack.ack.failed', { error: errorMessage(err) }),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If slack.react throws synchronously (for example, if the method is missing at runtime due to a dependency mismatch, or if it performs synchronous argument validation), it will throw before Promise.resolve or .catch can intercept it. This would crash the entire handler turn, violating the 'never fail the turn over a reaction' goal. Wrapping the call using Promise.resolve().then(...) ensures that both synchronous and asynchronous errors are safely caught.

Suggested change
void Promise.resolve(slack.react(msg.channel, msg.ts, ACK_EMOJI)).catch((err) =>
ctx.log?.('warn', 'linear-slack.ack.failed', { error: errorMessage(err) }),
);
void Promise.resolve()
.then(() => slack.react(msg.channel, msg.ts, ACK_EMOJI))
.catch((err) =>
ctx.log?.('warn', 'linear-slack.ack.failed', { error: errorMessage(err) }),
);

Comment thread linear-slack/agent.ts
} catch (postErr) {
ctx.log?.('error', 'linear-slack.reply.undelivered', { error: errorMessage(postErr) });
}
await postReply(ctx, slack, msg, reply);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Although postReply no longer throws on a missing receipt, it can still throw other errors (e.g., network failures or API errors). If postReply throws inside this error handler, it will propagate and mask the original harness error (err), making debugging and triage significantly harder. Wrapping it in a try/catch block ensures the original failure remains the primary reported error.

    try {
      await postReply(ctx, slack, msg, reply);
    } catch (postErr) {
      ctx.log?.('error', 'linear-slack.reply.undelivered', { error: errorMessage(postErr) });
    }

@agent-relay-code

Copy link
Copy Markdown
Contributor

pr-reviewer could not complete review for #55 in AgentWorkforce/agents.
The review harness exited with code 1.
No review was posted; this needs operator attention.

@agent-relay-code

Copy link
Copy Markdown
Contributor

ℹ️ pr-reviewer: review only — no file changes were applied to the PR (nothing to commit after review). The notes below are advisory and were not pushed.

pr-reviewer could not complete review for #55 in AgentWorkforce/agents.
The review harness exited with code 1.
No review was posted; this needs operator attention.

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7a9946db-4d8e-45c7-9f5a-5fe2736d4b83

📥 Commits

Reviewing files that changed from the base of the PR and between 66995b6 and 7a5fac9.

📒 Files selected for processing (2)
  • linear-slack/agent.ts
  • tests/linear-slack-agent.test.mjs

📝 Walkthrough

Walkthrough

This PR strengthens Slack/Linear agent writeback by adding immediate Slack acknowledgment reactions, extending writeback timeouts, and gracefully handling missing delivery receipts without throwing errors. It also refines Linear action messaging to distinguish confirmed vs. pending states when receipts are unavailable.

Changes

Slack/Linear Agent Writeback Reliability

Layer / File(s) Summary
Slack React Capability and Timeout Configuration
linear-slack/agent.ts
Slack client interface extended with react(...) method. New constants ACK_EMOJI and WRITEBACK_TIMEOUT_MS introduced for acknowledgment and receipt wait tuning, wired through both slackClient(opts) and linearClient(opts) during handler invocation.
Early Slack Acknowledgment on Message Receipt
linear-slack/agent.ts
Fire-and-forget Slack emoji reaction (react(...)) emitted asynchronously on eligible incoming messages, with reaction failures logged via ctx.log rather than terminating the turn.
Robust Slack Reply Delivery with Graceful Degradation
linear-slack/agent.ts
postReply refactored to accept ctx and respect timeout model; delivery failures now log a warning and continue instead of throwing an error. Handler integration points updated to use the new postReply(ctx, ...) signature without local try/catch wrappers.
Linear Action Confirmation: Confirmed vs. Pending Messaging
linear-slack/agent.ts
New confirm() helper accepts both confirmed and pending message templates, switches unconfirmed write logging from error to warning, and returns "pending" message when Linear does not return an http(s) receipt. create_issue and comment action handlers updated to pass distinct confirmed/pending messages.
Test Coverage for Acknowledgment and Delivery Behavior
tests/linear-slack-agent.test.mjs
slackSpy() mock extended with reactions array and react(...) method tracking { channel, messageTs, emoji } entries. Create-issue test now asserts early "eyes" emoji reaction. Unconfirmed-create test messaging expectations updated to match new "appear on the board|minute or two|Submitting" wording.

Sequence Diagram

sequenceDiagram
  participant IncomingMsg as Incoming Slack Message
  participant Handler as Handler
  participant Slack as Slack API
  participant Linear as Linear API
  participant Result as Result Reply
  
  IncomingMsg->>Handler: Message received (handleSlackEvent)
  Handler->>Slack: react(...) → acknowledge with emoji
  note over Slack: Fire-and-forget, logged if fails
  Handler->>Linear: Execute action (create_issue/comment)
  Linear-->>Handler: Response with receipt (or not)
  Handler->>Handler: confirm() → check if http(s) receipt
  alt Receipt URL found
    Handler->>Result: Confirmed message
  else No receipt or non-http URL
    Handler->>Result: Pending message (warn, don't error)
  end
  Handler->>Slack: postReply(...) with result
  note over Slack: Log warning if no receipt, continue
  Slack-->>Handler: Delivery result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • AgentWorkforce/agents#53: Related to postReply behavior when Slack ts receipts are missing; this PR downgrades to warning-and-continue while the related PR makes it throw and fixes persona writeback scope.

Suggested labels

size:L

Poem

🐰 Slack reacts with a wink before the work's begun,
No more throwing when the post fails—just a warning in the sun,
Linear's writes now dance between confirmed and yet-to-be,
Timeouts stretched, receipts embraced, reliability set free! ✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/linear-slack-receipt-timeout-and-ack

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@agent-relay-code

Copy link
Copy Markdown
Contributor

ℹ️ pr-reviewer: review only — no file changes were applied to the PR (nothing to commit after review). The notes below are advisory and were not pushed.

pr-reviewer could not complete review for #55 in AgentWorkforce/agents.
The review harness exited with code 1.
No review was posted; this needs operator attention.

@agent-relay-code

Copy link
Copy Markdown
Contributor

pr-reviewer could not complete review for #55 in AgentWorkforce/agents.
The review harness exited with code 1.
No review was posted; this needs operator attention.

1 similar comment
@agent-relay-code

Copy link
Copy Markdown
Contributor

pr-reviewer could not complete review for #55 in AgentWorkforce/agents.
The review harness exited with code 1.
No review was posted; this needs operator attention.

@agent-relay-code

Copy link
Copy Markdown
Contributor

ℹ️ pr-reviewer: review only — no file changes were applied to the PR (nothing to commit after review). The notes below are advisory and were not pushed.

pr-reviewer could not complete review for #55 in AgentWorkforce/agents.
The review harness exited with code 1.
No review was posted; this needs operator attention.

@agent-relay-code

Copy link
Copy Markdown
Contributor

pr-reviewer could not complete review for #55 in AgentWorkforce/agents.
The review harness exited with code 1.
No review was posted; this needs operator attention.

@agent-relay-code

Copy link
Copy Markdown
Contributor

ℹ️ pr-reviewer: review only — no file changes were applied to the PR (nothing to commit after review). The notes below are advisory and were not pushed.

pr-reviewer could not complete review for #55 in AgentWorkforce/agents.
The review harness exited with code 1.
No review was posted; this needs operator attention.

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.

1 participant