ChatGPT converts pasted text to file attachment

Dear OpenAI Community and Support Team,

I am writing to report a recent and disruptive change in the functionality of the ChatGPT chat interface concerning text input. This issue is a significant regression from previous capabilities.

1. The Core Problem

Previously, it was possible to copy and paste extended or longer text passages directly into the chat box for processing, analysis, or translation. This was a smooth and essential feature for complex tasks.

Currently, if I attempt to paste any longer piece of text into the conversation (e.g., several paragraphs or a document section), the following happens:

  • The text does not appear in the chat box as expected.

  • Instead, the system automatically converts the pasted content into a file attachment named pasted.txt.

This forced conversion makes it impossible to continue the conversation seamlessly or edit the pasted text directly, and it disrupts the analytical workflow.

2. When Did This Start?

This problem has never occurred before and appears to be a very recent change or an unintended bug introduced during a recent update. The expected behavior is that the text is simply pasted as plain text into the input field.

3. Steps to Reproduce the Issue

To help the team diagnose the problem, here are the steps that consistently trigger the issue:

1. Copy a long text (e.g., a few hundred words or more) from any external source (Word document, website, etc.).

2. Open the ChatGPT chat interface.

3. Paste the content into the chat input field.

Result: The text is automatically converted into the pasted.txt file and is not available for immediate use in the chat context.

4. Call for Resolution

This functionality is crucial for power users and complex research tasks. I kindly request the support team to investigate this issue as a high-priority bug and restore the standard, direct text pasting capability.

Is anyone else in the community experiencing this new issue?

Thank you for your time and assistance in resolving this.

Best regards, Marek Koppel

11 Likes

Me too! It’s really a negative different to how it was before.

3 Likes

Same here.

I regularly paste entire source files (React components, backend code, etc.). This used to work perfectly and now everything is forced into pasted.txt, breaking the workflow completely.

This is a clear regression and seriously impacts developer productivity.

3 Likes

Also experiencing this. It seems to treat the pasted text as a source for RAG lookups instead of fully part of the context, which breaks a lot of workflows. Definitely a downgrade.

EDIT: This only appears to happen when logged in; if I log out and use ChatGPT anonymously, the paste function works as it previously did, though obviously this is not desirable.

EDIT 2: Pasting a 5000-word document with no graphics or markdown from Word into Windows Notepad first, copying that text again from Notepad, and pasting into ChatGPT makes no difference. Still get the ā€œpasted.txtā€ result with just this ostensibly plain text.

EDIT 3: This may be an A/B testing issue. With my personal paid account, I get this auto-ā€pasted.txtā€ behavior. With a team account, it doesn’t happen.

1 Like

I’m having the same issue. It all started after GPT-5.2 came out. I never had this problem before.

1 Like

I hard-refreshed ChatGPT. Pasted 6200 lines of code. It is pasted into the input box as expected.

There is an endpoint that your entire copypasta is being sent to, /backend-api/f/conversations/prepare. It seems that I get an ā€œOKā€ status and ā€œconduit_tokenā€ returned. One can certainly expect this could classify the input for model context minimization without refusal, emulating the dumbness also seen when pasting into Anthropic Claude.

One thing you might not know is that the OS clipboard can have multiple versions of the same ā€œcopyā€.

There might be a few things you can try, assuming that this ā€œfeatureā€ depends on the pasted type, to sanitize the input of rich text:

  • Use the browser right-click for ā€œcopy without formattingā€
  • Paste with CTRL-SHIFT-V for unformatted text.
  • Paste to and from an intermediary plain-text notepad app.

OpenAI might be doing you the favor of also making the rich text into a more AI-consumable format such as markdown-formatted, preserving features like bolding or italic.

3 Likes

This is a huge issue for me as well as it hinders my workflow. Hopefully they enable a way to turn this off, otherwise I’ll have to look at other platforms, unfortunately.

@_j Hm, interesting. I could be wrong, but for me it seems to be determined by the length of code that I’m pasting…anything over roughly 100 lines (of simple code) results in it generating pasted.txt instead of me seeing what I’m pasting.

4 Likes

That just kills me. It was never too good at remembering long context. Now it’s just useless.

1 Like

Same problem here, yesterday was ok, and now each time what I copy page my prompt (over 10k characters) is generating a txt file, but I need edit my prompt.
OpenAi does ChatGPT worst with each update, more that feature, they release bugs

2 Likes

This feature is absolutely disrupting me

1 Like

Use CTRL + SHIFT + V, that should bypass it. But yeah, I really don’t like this new feature either.

4 Likes

Hi guys !

Since i can’t see the pasted.text file, i’m asking ChatGPT to open the content of pasted.text. Otherwise, as a developperin school, this greatly affects my learning! So annoying. I’m paying for something i need to use in my daily life and this new feature slows me down. I dont’ see the point of it, exept maybe to reduce clutter. I’ have clearly read a lot of comments and they are negative which makes sens.

1 Like

1 Like

I hate the new update! Things worked perfectly, now tasks are way more problematic as GPT loses track of large projects and cant keep up to date with changes made in the chat. PLEASE OpenAI, change it back.

Dropping a workaround I vibe-coded

If you’re hitting the ā€œbig paste turns into a pasted.txt attachmentā€ thing, this Tampermonkey script intercepts large pastes and re-inserts the clipboard text in smaller chunks so it stays as actual text in the prompt box.

Quick notes / disclaimers

  • Unofficial hack, not OpenAI-supported.
  • Runs locally in your browser.
  • No network calls / no telemetry. It only reads what you paste and immediately inserts it into the editor.
  • Still: review any userscript before installing (Tampermonkey has access to the matched site).

Install

  1. Install Tampermonkey
  2. Create a new userscript
  3. Paste this in, save
  4. Refresh chatgpt com
// ==UserScript==
// @name         ChatGPT: prevent large paste -> pasted.txt (ProseMirror)
// @match        https://chatgpt.com/*
// @run-at       document-start
// @grant        none
// ==/UserScript==

(() => {
  "use strict";

  const TAG = "[TM-no-pastedtxt]";

  const CFG = {
    DEBUG: false,

    // Intercept if either threshold is hit
    TRIGGER_LINES: 120,
    TRIGGER_CHARS: 8000,

    // Reinsert in chunks
    CHUNK_LINES: 40,
    CHUNK_DELAY_MS: 0, // increase (e.g. 5–20) if you ever see dropped chunks

    // Try multiple selectors to survive minor DOM changes
    EDITOR_SELECTORS: [
      "div#prompt-textarea.ProseMirror[contenteditable='true']",
      "div#prompt-textarea[contenteditable='true']",
      "[contenteditable='true'][data-testid='prompt-textarea']",
    ],
  };

  const log = (...a) => CFG.DEBUG && console.log(TAG, ...a);

  const attachedEditors = new WeakSet();

  function findEditorFromEvent(e) {
    const path = typeof e.composedPath === "function" ? e.composedPath() : [];
    for (const node of path) {
      if (!(node instanceof Element)) continue;
      for (const sel of CFG.EDITOR_SELECTORS) {
        const hit = node.closest?.(sel);
        if (hit) return hit;
      }
    }
    // Fallback: activeElement or global query
    const ae = document.activeElement;
    if (ae instanceof Element) {
      for (const sel of CFG.EDITOR_SELECTORS) {
        const hit = ae.closest?.(sel);
        if (hit) return hit;
      }
    }
    for (const sel of CFG.EDITOR_SELECTORS) {
      const hit = document.querySelector(sel);
      if (hit) return hit;
    }
    return null;
  }

  function chunkByLines(text, nLines) {
    // Keep it simple and deterministic
    const lines = text.split("\n");
    const chunks = [];
    for (let i = 0; i < lines.length; i += nLines) {
      chunks.push(lines.slice(i, i + nLines).join("\n"));
    }
    return chunks;
  }

  function safeInsertText(editor, text) {
    editor.focus();

    // 1) Preferred: execCommand insertText (still the most compatible for contenteditable)
    try {
      if (document.queryCommandSupported?.("insertText")) {
        document.execCommand("insertText", false, text);
      } else {
        document.execCommand("insertText", false, text);
      }
    } catch {
      // 2) Fallback: DOM Range insertion
      const sel = editor.ownerDocument.getSelection?.() || window.getSelection?.();
      if (sel && sel.rangeCount > 0) {
        const range = sel.getRangeAt(0);
        range.deleteContents();
        range.insertNode(editor.ownerDocument.createTextNode(text));
        // Move caret to end of inserted text node
        range.collapse(false);
        sel.removeAllRanges();
        sel.addRange(range);
      } else {
        // 3) Last resort
        editor.textContent += text;
      }
    }

    // Nudge frameworks that listen for input
    try {
      editor.dispatchEvent(
        new InputEvent("input", { bubbles: true, data: text, inputType: "insertText" })
      );
    } catch {
      editor.dispatchEvent(new Event("input", { bubbles: true }));
    }
  }

  async function reinsertInChunks(editor, text) {
    const chunks = chunkByLines(text, CFG.CHUNK_LINES);
    for (let i = 0; i < chunks.length; i++) {
      const chunk = chunks[i];
      safeInsertText(editor, chunk + (i < chunks.length - 1 ? "\n" : ""));
      if (CFG.CHUNK_DELAY_MS > 0) {
        await new Promise(r => setTimeout(r, CFG.CHUNK_DELAY_MS));
      } else {
        // Yield back to the event loop to keep UI responsive
        await new Promise(r => setTimeout(r, 0));
      }
    }
  }

  function onPaste(e) {
    const editor = findEditorFromEvent(e);
    if (!editor) return;

    const text = e.clipboardData?.getData("text/plain") ?? "";
    if (!text) return;

    const lineCount = (text.match(/\n/g) || []).length;
    const isLarge = lineCount >= CFG.TRIGGER_LINES || text.length >= CFG.TRIGGER_CHARS;

    log("paste", { len: text.length, lines: lineCount, isLarge });

    if (!isLarge) return;

    // Prevent ChatGPT's paste handler from seeing it at all
    e.preventDefault();
    e.stopImmediatePropagation();

    void reinsertInChunks(editor, text);
  }

  // Capture-phase listener to beat site handlers
  document.addEventListener("paste", onPaste, true);

  // Attach directly to editor nodes as they appear (more robust vs bubbling weirdness)
  const mo = new MutationObserver(() => {
    for (const sel of CFG.EDITOR_SELECTORS) {
      const editor = document.querySelector(sel);
      if (editor && !attachedEditors.has(editor)) {
        attachedEditors.add(editor);
        editor.addEventListener("paste", onPaste, true);
        log("attached to editor node", sel);
      }
    }
  });

  mo.observe(document.documentElement, { childList: true, subtree: true });
  log("loaded");
})();

If this doesn’t work for you, consider asking ChatGPT to debug it for you ;).

1 Like

It’s seriously disappointing that OpenAI seemed to have blindly followed Claude’s design patterns with this update. It is genuinely one of the biggest factors that makes me prefer using ChatGPT over Claude.

This needs to be a setting, not enforced.

However, thank you to @Balrog2000 above for a great solution. The alternative on Mac is CMD + Option + Shift + V (the standard shortcut for ā€œPaste and Match styleā€)

2 Likes

I’ve been following this thread because this ā€œfeatureā€ (or lack thereof) was driving me crazy too. The inconsistency of the A/B test is the worst part—sometimes it converts, sometimes it blasts the UI.

I ended up building a small chrome extension to force this behaviour manually(AI used to generate code). It intercepts any paste over 2000 characters and converts it to a file attachment.

I also tried to fix the ā€œuneditableā€ complaint mentioned here. I added a modal so you can preview and edit the file content before sending it to the model.

All of the above options are configurable.

It’s a temporary fix until OpenAI rolls this out natively for everyone, but if you want to try it, the code is available on Github at github[dot]com/SandeepGusain/chatgpt-paste-optimizer.

2 Likes

Many thanks to @Balrog2000 for the Tampermonkey script, which is a great workaround until such time as this is fixed.

However, there are some situations where I do actually find the new ChatGPT behaviour helpful, so ideally it should be optional. Hopefully that’ll be how it’s implemented eventually - but until then, I’ve modified @Balrog2000’s script such that:

  • conventional paste events of any kind will trigger the script and paste normally
  • ā€˜special paste’ events (effectively CTRL-Shift-V / CMD-Shift-V, but more specifically: holding CTRL+Shift / CMD+Shift during any kind of paste event) will disable the script and allow ChatGPT’s default behaviour to go ahead.

In case this is helpful for anyone else, too, here’s the script:

// ==UserScript==
// @name         ChatGPT: prevent large paste -> pasted.txt (ProseMirror) + bypass Ctrl+Shift+V
// @match        https://chatgpt.com/*
// @run-at       document-start
// @grant        none
// ==/UserScript==

(() => {
  "use strict";

  const TAG = "[TM-no-pastedtxt]";
  const CFG = {
    DEBUG: false,

    // Intercept if either threshold is hit
    TRIGGER_LINES: 120,
    TRIGGER_CHARS: 8000,

    // Reinsert in chunks
    CHUNK_LINES: 40,
    CHUNK_DELAY_MS: 5, // increase (e.g. 5–20) if you ever see dropped chunks

    // Try multiple selectors to survive minor DOM changes
    EDITOR_SELECTORS: [
      "div#prompt-textarea.ProseMirror[contenteditable='true']",
      "div#prompt-textarea[contenteditable='true']",
      "[contenteditable='true'][data-testid='prompt-textarea']",
    ],

    // Bypass combo:
    // - Windows/Linux: Ctrl+Shift held at paste time
    // - macOS: Cmd+Shift held at paste time
    // (We detect modifier state on paste, so we do NOT rely on seeing "V" keydown.)
    BYPASS_REQUIRE_NO_ALT: true,
  };

  const log = (...a) => CFG.DEBUG && console.log(TAG, ...a);
  const attachedEditors = new WeakSet();

  // --- modifier tracking (robust bypass) ---
  const mods = { ctrl: false, shift: false, meta: false, alt: false };

  function updateModsFromEvent(e) {
    mods.ctrl = !!e.ctrlKey;
    mods.shift = !!e.shiftKey;
    mods.meta = !!e.metaKey;
    mods.alt = !!e.altKey;
  }

  function isBypassHeldNow() {
    const altOk = CFG.BYPASS_REQUIRE_NO_ALT ? !mods.alt : true;
    // Win/Linux: Ctrl+Shift (no Meta)
    const winLinux = mods.ctrl && mods.shift && !mods.meta && altOk;
    // macOS: Cmd+Shift (no Ctrl)
    const mac = mods.meta && mods.shift && !mods.ctrl && altOk;
    return winLinux || mac;
  }

  function onKeyDown(e) {
    updateModsFromEvent(e);
  }

  function onKeyUp(e) {
    updateModsFromEvent(e);
  }

  function findEditorFromEvent(e) {
    const path = typeof e.composedPath === "function" ? e.composedPath() : [];
    for (const node of path) {
      if (!(node instanceof Element)) continue;
      for (const sel of CFG.EDITOR_SELECTORS) {
        const hit = node.closest?.(sel);
        if (hit) return hit;
      }
    }

    // Fallback: activeElement or global query
    const ae = document.activeElement;
    if (ae instanceof Element) {
      for (const sel of CFG.EDITOR_SELECTORS) {
        const hit = ae.closest?.(sel);
        if (hit) return hit;
      }
    }

    for (const sel of CFG.EDITOR_SELECTORS) {
      const hit = document.querySelector(sel);
      if (hit) return hit;
    }
    return null;
  }

  function chunkByLines(text, nLines) {
    const lines = text.split("\n");
    const chunks = [];
    for (let i = 0; i < lines.length; i += nLines) {
      chunks.push(lines.slice(i, i + nLines).join("\n"));
    }
    return chunks;
  }

  function safeInsertText(editor, text) {
    editor.focus();

    // 1) Preferred: execCommand insertText (the most compatible for contenteditable)
    try {
      document.execCommand("insertText", false, text);
    } catch {
      // 2) Fallback: DOM Range insertion
      const sel = editor.ownerDocument.getSelection?.() || window.getSelection?.();
      if (sel && sel.rangeCount > 0) {
        const range = sel.getRangeAt(0);
        range.deleteContents();
        range.insertNode(editor.ownerDocument.createTextNode(text));
        // Move caret to end of inserted text node
        range.collapse(false);
        sel.removeAllRanges();
        sel.addRange(range);
      } else {
        // 3) Last resort
        editor.textContent += text;
      }
    }

    // Nudge frameworks that listen for input
    try {
      editor.dispatchEvent(
        new InputEvent("input", { bubbles: true, data: text, inputType: "insertText" })
      );
    } catch {
      editor.dispatchEvent(new Event("input", { bubbles: true }));
    }
  }

  async function reinsertInChunks(editor, text) {
    const chunks = chunkByLines(text, CFG.CHUNK_LINES);
    for (let i = 0; i < chunks.length; i++) {
      const chunk = chunks[i];
      safeInsertText(editor, chunk + (i < chunks.length - 1 ? "\n" : ""));
      const delay = Math.max(0, CFG.CHUNK_DELAY_MS);
      await new Promise((r) => setTimeout(r, delay));
    }
  }

  function onPaste(e) {
    const editor = findEditorFromEvent(e);
    if (!editor) return;

    // --- BYPASS: if "special paste" modifiers are held, allow site/default handler ---
    if (isBypassHeldNow()) {
      log("bypass held: allowing default paste", { mods: { ...mods } });
      return; // IMPORTANT: no preventDefault, no stopPropagation
    }

    const text = e.clipboardData?.getData("text/plain") ?? "";
    if (!text) return;

    const lineCount = (text.match(/\n/g) || []).length;
    const isLarge = lineCount >= CFG.TRIGGER_LINES || text.length >= CFG.TRIGGER_CHARS;
    log("paste", { len: text.length, lines: lineCount, isLarge, mods: { ...mods } });

    if (!isLarge) return;

    // Prevent ChatGPT's paste handler from seeing it at all
    e.preventDefault();
    e.stopImmediatePropagation();
    void reinsertInChunks(editor, text);
  }

  // Capture-phase listeners to beat site handlers
  // Track modifiers globally (don’t depend on editor detection for key events)
  window.addEventListener("keydown", onKeyDown, true);
  window.addEventListener("keyup", onKeyUp, true);
  document.addEventListener("paste", onPaste, true);

  // Attach directly to editor nodes as they appear (more robust vs bubbling weirdness)
  const mo = new MutationObserver(() => {
    for (const sel of CFG.EDITOR_SELECTORS) {
      const editor = document.querySelector(sel);
      if (editor && !attachedEditors.has(editor)) {
        attachedEditors.add(editor);
        editor.addEventListener("paste", onPaste, true);
        log("attached to editor node", sel);
      }
    }
  });

  mo.observe(document.documentElement, { childList: true, subtree: true });
  log("loaded");
})();

1 Like

I noticed a change today: it’s now possible to input much longer texts (Ctrl+C works for larger amounts of data).