Problem
Across providers, agents lose access to a resource silently — without a webhook telling us. Currently each provider hits this and the cleanup is missing or ad-hoc.
Concrete cases observed:
| Provider |
Loss-of-access scenario |
Webhook? |
Current behavior |
| Slack |
/kick @bot from a channel (per_channel session) |
❌ Slack doesn't deliver member_left_channel for bot self-leave (verified end-to-end on staging in #24) |
Channel-session row stays active, scheduleWakeup keeps firing, every wake → not_in_channel 401, agent burns tokens |
| Linear |
Bot unassigned from issue / issue archived under bot |
partial (some events) |
not yet wired |
| GitHub |
App removed from repo / repo deleted |
partial |
not yet wired |
| MCP servers (generic) |
Token revoked upstream |
❌ no webhook |
every tool call 401s; no cleanup |
The unifying observation: the agent's tool call IS the signal. When a provider returns an error semantically equivalent to "you no longer have access to this resource", that should:
- Mark the session/scope row closed in the provider's persistence (e.g. Slack:
slack_thread_sessions.status = completed)
- Cancel any pending scheduleWakeup for that session
- Stop the agent for this turn (don't just retry the same call into a wall)
Proposed shape
A generic harness hook, not provider-specific RPC. Sketch:
- Each provider tool wrapper declares a small predicate: "does this error mean lost-access for the current resource?" (e.g. Slack: error code in {
channel_not_found, not_in_channel, account_inactive, token_revoked}; Linear: 404 on the assigned issue + bot not in members; etc.)
- When the predicate matches, the harness calls a single internal API:
closeSession({ reason: 'lost_access', detail })
- That handler routes to the provider's own
closeSessionForScope(...) (already implemented in Slack via sessionScopes.updateStatus(... 'completed') + clearPendingScan(...)); other providers add similar
closeSession also cancels pending scheduleWakeups owned by this session
This avoids:
- Each provider re-inventing a service-binding RPC back to integrations
- Slack-specific code paths in the agent harness
- Three rewrites (Slack → Linear → GitHub) before being forced to abstract
Why not now / why a separate PR
PR #24 is per_channel-scoped and end-to-end green on staging. The right design for this hook needs the Linear and GitHub cases at the table — doing a Slack-only version now means rewriting on the second provider. Better to:
Acceptance criteria
Related
Problem
Across providers, agents lose access to a resource silently — without a webhook telling us. Currently each provider hits this and the cleanup is missing or ad-hoc.
Concrete cases observed:
/kick @botfrom a channel (per_channel session)member_left_channelfor bot self-leave (verified end-to-end on staging in #24)active, scheduleWakeup keeps firing, every wake →not_in_channel401, agent burns tokensThe unifying observation: the agent's tool call IS the signal. When a provider returns an error semantically equivalent to "you no longer have access to this resource", that should:
slack_thread_sessions.status = completed)Proposed shape
A generic harness hook, not provider-specific RPC. Sketch:
channel_not_found,not_in_channel,account_inactive,token_revoked}; Linear: 404 on the assigned issue + bot not in members; etc.)closeSession({ reason: 'lost_access', detail })closeSessionForScope(...)(already implemented in Slack viasessionScopes.updateStatus(... 'completed')+clearPendingScan(...)); other providers add similarcloseSessionalso cancels pending scheduleWakeups owned by this sessionThis avoids:
Why not now / why a separate PR
PR #24 is per_channel-scoped and end-to-end green on staging. The right design for this hook needs the Linear and GitHub cases at the table — doing a Slack-only version now means rewriting on the second provider. Better to:
Acceptance criteria
apps/agent/src/harness/)channel_not_found,not_in_channel,account_inactive,token_revokedcompleted, no further wakeups fireRelated
tokens_revoked/app_uninstalledcleanup being added directly in feat(slack): per_channel granularity + treat-bot-like-human signals #24 (full-installation revocation)member_left_channelnon-delivery finding