jackwener/OpenCLI · error · ArgumentError

title must be a single line

Error message

title must be a single line

What it means

Codex chat titles must be a single line; the library inspects the trimmed title and throws ArgumentError if it contains a newline character, because the rename input cannot accept multi-line text.

Source

Thrown at clis/codex/rename.js:26

} from './_actions.js';

cli({
    site: 'codex',
    name: 'rename',
    access: 'write',
    description: 'Rename the selected Codex conversation. Opens the Chat actions menu → "Rename chat", then types the new title.',
    domain: 'localhost',
    strategy: Strategy.UI,
    browser: true,
    args: [
        { name: 'title', required: true, positional: true, help: 'New title (single line, no newlines)' },
        ...conversationSelectionArgs,
    ],
    columns: ['status', 'title', 'thread_id', 'project'],
    func: async (page, kwargs) => {
        const title = String(kwargs.title || '').trim();
        if (!title) throw new ArgumentError('title cannot be empty');
        if (title.includes('\n')) throw new ArgumentError('title must be a single line');

        // 1. Select the target chat AND click "Rename chat" in the menu.
        const action = await selectAndClickAction(page, kwargs, ['Rename chat']);
        await page.wait(0.5);

        // 2. The rename input is the only non-ProseMirror editable that just appeared.
        //    Fill it via execCommand insertText (Codex uses a contenteditable, not a plain input).
        const filled = unwrapEvaluateResult(await page.evaluate(`(async () => {
      const wait = (ms) => new Promise((r) => setTimeout(r, ms));
      let input = null;
      for (let attempt = 0; attempt < 15; attempt += 1) {
        const candidates = Array.from(document.querySelectorAll('input[type="text"], input:not([type]), [contenteditable="true"]'))
          .filter((el) => el.offsetParent && !el.classList.contains('ProseMirror'));
        if (candidates.length) {
          candidates.sort((a, b) => (a.getBoundingClientRect().left || 9999) - (b.getBoundingClientRect().left || 9999));
          input = candidates[0];
          break;
        }

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Strip or replace newlines before passing the title, e.g. title.replace(/\r?\n/g, ' ').
  2. Collapse the desired text to one line and truncate if needed.
  3. In shell, use echo "$TITLE" | tr '\n' ' ' or ${TITLE//$'\n'/ } before invoking the command.

Example fix

// before
codex rename "Line one
Line two" 3
// ArgumentError: title must be a single line

// after
const title = rawTitle.replace(/\s*\n\s*/g, ' ').trim();
codex rename title 3
Defensive patterns

Strategy: validation

Validate before calling

const title = String(rawTitle ?? '').trim();
if (title.includes('\n')) {
  throw new Error('Title must be a single line');
}

Type guard

function isSingleLine(v) {
  return typeof v === 'string' && v.trim().length > 0 && !v.includes('\n');
}

Try / catch

try {
  await codexRename({ title, ...selection });
} catch (e) {
  if (e instanceof ArgumentError && /single line/.test(e.message)) {
    return codexRename({ title: title.replace(/\s*\n\s*/g, ' '), ...selection });
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling codex rename with a title containing '\n', e.g. a multi-line string, a value pasted from a file or terminal selection that includes a line break, or a heredoc/unquoted variable with embedded newlines.

Common situations: Generating titles from multi-line text (commit messages, file contents); copying text from editors/terminals that include trailing newlines; using shell variables spanning multiple lines.

Related errors


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