fix: never let a guessed threshold hold an account out of sending

The profile check was allowed to set state 'unestablished', which made
isSendable() false, which made canAcceptPost() false — so a flagged
account never attempted an invitation and the functional probe could
never run. The one signal with real authority was gated behind the one
that is guessing, and because each check pushed retryAt forward by
another interval, accounts that send perfectly well were held
indefinitely.

Reddit does not publish the thresholds its chat gate applies, so ours are
a guess and are now advisory only. Sending is held solely by a live
rejection, tracked as sendingHeld on the account summary and keyed off by
the attention feed, the sending state and the Accounts page.

Reason strings now say "below the configured floor of N" rather than
"needs N", and the Accounts page distinguishes "Sending on hold" from
"Below thresholds", stating outright that the latter is our own number
and the account is still sending.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Jamie BruntonandClaude Opus 5 committed 2026-08-11 09:49:40 +01:00
1 parent 89eb4583f8
commit 3a40651930
9 files changed
+164 -71

No files matched your search

@@ -32,25 +32,36 @@ function retryLabel(retryAt: number | null): string {
}
/**
* A new account cannot start chats until Reddit considers it established. The
* bot holds it out of sending and re-tests it, so this panel exists to explain
* why an account looks idle.
* Two different situations share this panel: Reddit actively refusing
* invitations from the account (sending is on hold), and the account merely
* sitting below our own configured thresholds (nothing is blocked). Conflating
* them reads as a broken account when it is only a soft signal.
*/
function EstablishmentDetail({ establishment }: { establishment: AccountEstablishmentSummary }) {
const isUnestablished = establishment.state === 'unestablished';
const belowThresholds = establishment.state === 'unestablished' && !establishment.sendingHeld;
return (
<div className="account-establishment">
{isUnestablished ? (
{establishment.sendingHeld ? (
<>
<p className="account-establishment-reason">
{establishment.reasons.length > 0
? establishment.reasons.join('; ')
: 'Reddit will not accept chat invitations from this account yet.'}
{establishment.lastProbeFailure
?? 'Reddit rejected an invitation from this account as unestablished.'}
</p>
<p className="field-hint">Outbound sending is on hold. {retryLabel(establishment.retryAt)}</p>
</>
) : null}
{establishment.lastProbeFailure && !isUnestablished ? (
{belowThresholds ? (
<>
<p className="account-establishment-reason">
{establishment.reasons.join('; ')}
</p>
<p className="field-hint">
Still sending normally — these are our own configured thresholds, not limits Reddit
publishes. The account is only held back if Reddit actually rejects an invitation.
</p>
</>
) : null}
{establishment.lastProbeFailure && !establishment.sendingHeld ? (
<p className="field-hint">Last rejection: {establishment.lastProbeFailure}</p>
) : null}
<dl className="account-establishment-metrics">
@@ -69,9 +80,9 @@ function EstablishmentDetail({ establishment }: { establishment: AccountEstablis
function establishmentPill(establishment: AccountEstablishmentSummary | null | undefined) {
if (!establishment || establishment.state === 'unknown') return null;
return establishment.state === 'unestablished'
? <span className="pill warning">Not established</span>
: <span className="pill success">Established</span>;
if (establishment.sendingHeld) return <span className="pill warning">Sending on hold</span>;
if (establishment.state === 'unestablished') return <span className="pill neutral">Below thresholds</span>;
return <span className="pill success">Established</span>;
}
function sessionLabel(status: ReturnType<typeof useLoginStatusQuery>['data']): string {
@@ -120,7 +131,7 @@ export function AccountsPage() {
: null;
const accounts = accountsQuery.data ?? [];
const pausedCount = accounts.filter((account) => !account.isRunning).length;
const unestablished = accounts.filter((account) => account.establishment?.state === 'unestablished');
const heldAccounts = accounts.filter((account) => account.establishment?.sendingHeld);
return (
<div className="page accounts-page">
@@ -146,16 +157,16 @@ export function AccountsPage() {
</div>
) : null}
{unestablished.length > 0 ? (
{heldAccounts.length > 0 ? (
<div className="state-banner is-warning" role="status">
<strong>
{unestablished.length === 1
? `u/${unestablished[0].redditUsername} is not established yet.`
: `${unestablished.length} accounts are not established yet.`}
{heldAccounts.length === 1
? `u/${heldAccounts[0].redditUsername} cannot start chats yet.`
: `${heldAccounts.length} accounts cannot start chats yet.`}
</strong>
<span>
Reddit will not accept chat invitations from a new account, so outbound sending is on hold
for it. The bot re-tests periodically and resumes on its own.
Reddit rejected an invitation from a new account, so outbound sending is on hold for it.
The bot re-tests periodically and resumes on its own.
</span>
</div>
) : null}