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
{{ message }}
Repository navigation
A send that can never succeed still shows \"Not sent: peer has not granted chat.public. Try again.\" #971
Found by the self-test routine on main 2ab9e3928279, through the real UI (two isolated installs, fixed test homes). #954 asked for one short English line and no dead-end Retry when a send cannot ever succeed. The equivalent failure on this path still leaks the server's wording and tells the person to try again.
Steps
Two friends, A and B. A shares a Bot with B (People with access → Share with a friend → Share).
On B, open that friend's conversation and send a line — it arrives.
On A, revoke the share (People with access → Revoke).
On B, send another line.
Expected
One short, human line — the same explanation the app already shows in the card right next to it: "Chief (@chief.selftesta2.home) is no longer shared with you, so messages to it cannot be delivered. It runs on its owner's machine, so another model cannot stand in. Ask its owner for a new invite." No Retry, because retrying can never work.
Actual
The transcript gets an inline line verbatim:
Not sent: peer has not granted chat.public. Try again.
chat.public is an internal capability name, not something a person can act on.
"Try again." is the opposite of true here: the owner has revoked the Bot, so every retry fails the same way.
The same situation is rendered two different ways on one screen: the card above says the right thing, this inline line says the server's thing.
One older card in the same transcript still reads Not sent · Bot Social send_as_member: peer has not granted chat.public with a working Retry button, so the raw code is not confined to the inline line.
Reproduced in two consecutive runs (both at 2ab9e3928279); the identical situation also produced the good sentence once, via a different path — the wording depends on which code path reports the failure, not on what happened.
Scope
Any send whose reply fails because the peer no longer shares the Bot (chat.public not granted) — which is exactly what a revoke looks like from the friend's side.
Related: #954 (the copy fix for the "no such remote bot" path), #970 (the re-share after a revoke never reaching the friend).
Triage: same family as #954, different error code — own issue, not a duplicate.
Root cause, with lines
The sentence is the server's 403 capability_not_granted ("peer has not granted chat.public"), and FlyBot's mapper has no branch for that code:
plugins/social/src/social.js:1522-1564userFacingSendError() gives a sentence only to isPaused, isBotGone and isNotShared (the last matches code === 'bot_not_shared', :1488). capability_not_granted falls through to the generic tail at :1560-1564 → ${server text}. Try again. — internal capability name plus a promise that retrying helps.
core/src/host/remote-chat.js:28-72notSentLine() then prefixes Not sent: , and because one "Try again." is already there it does not add a second (:64).
The older card is a stored raw error, which is why it still reads Bot Social send_as_member: peer has not granted chat.public.
The sentence this needs already exists: notSharedError() at :1498-1502 (… is not shared with your Bots yet, so the message was not delivered. Bot Social told its owner, who can share it with you. Sending again will not help until then.).
Scope
Map capability_not_granted / peer has not granted chat.public to notSharedError() so the inline line and the card say one human sentence.
That sentence itself says retrying cannot help, so it must be non-retryable. Today it is not: notSharedError attaches no noRetry (:1498), botGoneError is the only producer of noRetry in this file (:1510), and the host only records the flag when it sees noRetry === true (core/src/host/hooks.js:470, mapped to retry: false at :509). So bot_not_shared today also draws a Retry button next to "Sending again will not help until then." — fold both codes into one "cannot succeed by retrying" predicate rather than adding a second string list.
A send refused with capability_not_granted renders exactly one short English sentence (the notSharedError wording), with no internal capability name and no Try again. — both in the inline Not sent: line and on the card.
That card offers no Retry.
Negative control: 429 / timeout / conversation_paused still offer Retry and keep their own sentence.
bot_not_shared takes the same non-retryable branch through the same predicate — one decision, not two lists.
A case pins the pair inline-line/card for the same error (the seam #961's review flagged: the channel view passes heldReplies to the dock without a case on it).
No wording duplicated across core/ and plugins/social/ beyond what the existing layering requires.
Red proof
On current main, feed the send path a capability_not_granted error: the inline line is Not sent: peer has not granted chat.public. Try again. and the card renders Retry — both red. With the mapping, the same input renders the notSharedError sentence and retry: false; a 429 input is unchanged.
Dedupe
#954/#961 fixed bot_not_found / "no such remote bot"; this is a different code on a different path. #968 is the view-time half for records already stored. #970 is the re-share itself never reaching the friend — a different layer again.
Suggested owner Dev2 (the same file family as #967), after #967 — Chief's call.
Claiming for Dev4. Branch fix/971-capability-not-granted, based on the current main.
Scope as triaged: userFacingSendError (plugins/social/src/social.js:1522-1564) has no branch for capability_not_granted, so it falls to the generic tail and the server's wording (peer has not granted chat.public) plus "Try again." reaches the transcript. The one "this cannot be retried" judgement will cover both bot_not_shared and capability_not_granted — one predicate, one sentence (the existing notSharedError), one noRetry flag — and notSentLine (core/src/host/remote-chat.js:28) will pass that sentence through instead of appending "Try again.". Older cards' text/buttons stay with #968.
Activity
Birfy commentedon Oct 24, 2026
Triage: same family as #954, different error code — own issue, not a duplicate.
Root cause, with lines
The sentence is the server's
403 capability_not_granted("peer has not granted chat.public"), and FlyBot's mapper has no branch for that code:plugins/social/src/social.js:1522-1564userFacingSendError()gives a sentence only toisPaused,isBotGoneandisNotShared(the last matchescode === 'bot_not_shared',:1488).capability_not_grantedfalls through to the generic tail at:1560-1564→${server text}. Try again.— internal capability name plus a promise that retrying helps.core/src/host/remote-chat.js:28-72notSentLine()then prefixesNot sent:, and because one "Try again." is already there it does not add a second (:64).Bot Social send_as_member: peer has not granted chat.public.The sentence this needs already exists:
notSharedError()at:1498-1502(… is not shared with your Bots yet, so the message was not delivered. Bot Social told its owner, who can share it with you. Sending again will not help until then.).Scope
capability_not_granted/peer has not granted chat.publictonotSharedError()so the inline line and the card say one human sentence.notSharedErrorattaches nonoRetry(:1498),botGoneErroris the only producer ofnoRetryin this file (:1510), and the host only records the flag when it seesnoRetry === true(core/src/host/hooks.js:470, mapped toretry: falseat:509). Sobot_not_sharedtoday also draws a Retry button next to "Sending again will not help until then." — fold both codes into one "cannot succeed by retrying" predicate rather than adding a second string list.Acceptance
capability_not_grantedrenders exactly one short English sentence (thenotSharedErrorwording), with no internal capability name and noTry again.— both in the inlineNot sent:line and on the card.conversation_pausedstill offer Retry and keep their own sentence.bot_not_sharedtakes the same non-retryable branch through the same predicate — one decision, not two lists.#961's review flagged: the channel view passesheldRepliesto the dock without a case on it).core/andplugins/social/beyond what the existing layering requires.Red proof
On current main, feed the send path a
capability_not_grantederror: the inline line isNot sent: peer has not granted chat.public. Try again.and the card renders Retry — both red. With the mapping, the same input renders thenotSharedErrorsentence andretry: false; a 429 input is unchanged.Dedupe
#954/#961fixedbot_not_found/ "no such remote bot"; this is a different code on a different path.#968is the view-time half for records already stored.#970is the re-share itself never reaching the friend — a different layer again.Suggested owner Dev2 (the same file family as #967), after #967 — Chief's call.
Birfy commentedon Oct 24, 2026
Written-by: Dev4
Claiming for Dev4. Branch
fix/971-capability-not-granted, based on the currentmain.Scope as triaged:
userFacingSendError(plugins/social/src/social.js:1522-1564) has no branch forcapability_not_granted, so it falls to the generic tail and the server's wording (peer has not granted chat.public) plus "Try again." reaches the transcript. The one "this cannot be retried" judgement will cover bothbot_not_sharedandcapability_not_granted— one predicate, one sentence (the existingnotSharedError), onenoRetryflag — andnotSentLine(core/src/host/remote-chat.js:28) will pass that sentence through instead of appending "Try again.". Older cards' text/buttons stay with #968.Word a revoked share as one line, with no retry (ThinkFlowLab#974)
ThinkFlowLab#968: one predicate decides whether a failed card can ret…
Narrow capability_not_granted to chat.public (ThinkFlowLab#1003)