jackwener/OpenCLI · warning · TimeoutError

twitter post

Error message

twitter post

What it means

TimeoutError thrown when submitTweet could not CONFIRM the tweet went out before SUBMIT_TIMEOUT_MS. Crucially this is ambiguous: the poll expiring does not mean the tweet failed — it may already be live. Retrying blindly risks a duplicate post (issue #2255), so the message directs you to check `opencli twitter tweets --limit 1` first.

Source

Thrown at clis/twitter/post.js:352

            if (!uploadState?.ok) {
                throw new TimeoutError('twitter image upload', UPLOAD_TIMEOUT_MS / 1000, 'Nothing was posted. Retry, or attach a smaller image.');
            }
        }

        // Insert and verify the text after media upload so text + images are in
        // the final Draft.js composer state immediately before clicking Post.
        const typeResult = await insertComposerText(page, text);
        if (!typeResult?.ok) {
            throw new CommandExecutionError(typeResult?.message ?? 'Could not type tweet text.', 'Open the composer in the browser and check whether X is asking you to log in.');
        }

        await page.wait(1);
        const result = await submitTweet(page, text);
        if (result?.unconfirmed) {
            // The poll expiring does not mean the tweet stayed in the composer,
            // so this must not read as a definite failure: the agent workflow
            // retries CommandExecutionError and would post twice (#2255).
            throw new TimeoutError('twitter post', SUBMIT_TIMEOUT_MS / 1000, `${result.message} Check \`opencli twitter tweets --limit 1\` before retrying; the post may already be live.`);
        }
        if (!result?.ok) {
            throw new CommandExecutionError(result?.message ?? 'Tweet failed to post.', 'Nothing was posted. Open the composer in the browser and retry.');
        }
        return [{
            status: 'success',
            message: result.message,
            text,
            ...(result.id ? { id: result.id } : {}),
            ...(result.url ? { url: result.url } : {}),
        }];
    }
});

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Do NOT immediately retry; run `opencli twitter tweets --limit 1` to see if the post is already live
  2. If the tweet is live, treat the command as successful and skip retry
  3. If not live, retry the post command once
  4. Increase the submit timeout or improve network conditions if this recurs
  5. If confirmations are consistently missed, X may have changed its success indicators — report/update selectors

Example fix

// before
try { await post(); } catch (e) { await post(); } // may double-post
// after
try { await post(); } catch (e) {
  const recent = await opencli('twitter tweets', { limit: 1 });
  if (!recent.some(t => t.text === expectedText)) await post();
}
Defensive patterns

Strategy: retry

Validate before calling

// before retrying, verify the post actually went out
const recent = await opencli('twitter tweets', { limit: 1 });
if (recent?.[0]?.text === expectedText) return; // already live, skip retry

Type guard

const isAmbiguousSubmitTimeout = (e) =>
  e?.name === 'TimeoutError' && e.message.startsWith('twitter post');

Try / catch

try {
  await postTweet({ text });
} catch (e) {
  if (isAmbiguousSubmitTimeout(e)) {
    const recent = await opencli('twitter tweets', { limit: 1 });
    if (recent?.[0]?.text !== text) await postTweet({ text });
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: submitTweet returns { unconfirmed: true } — clicking Post did not yield a confirmation (toast/permalink) within SUBMIT_TIMEOUT_MS, e.g. slow X response, network hiccup after the click, or confirmation UI changes.

Common situations: Slow network right after clicking Post; X confirmation toast delayed or suppressed; agent automation retry loops causing duplicate tweets when the error is treated as a definite failure.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/937cd2fe4cdef836. Report an issue: GitHub.