Skip to content

setData will modify the data of historical items #3324

Closed

Description

@xymoryn

Describe the bug
When calling setData in an automated command, the history item also writes the same data.

To Reproduce

  1. Fresh installation of CopyQ
  2. Randomly copy a text once
  3. Add and enable the following automated command
[Command]
Name=test setData
Command="
    copyq:
    setData('application/x-copyq-tags', 'test')"
Automatic=true
Icon=
  1. Copy the text once again
  2. Open the CopyQ main window and find that the data of both the current entry and the previous item have been modified

Expected behavior

Only the data of the current entry is modified

Screenshots

Video

test.setData.mp4

Version, OS and Environment

  • Application Version: 13.00
  • OS: Windows 11
  • Installation method: portable zip

Additional context

I want to store metadata (copy time, source url, window title, etc.) of entries in CopyQ. For this purpose, I created an automated command and called setData multiple times to write data with custom MIME types.

Later, I found that this command would overwrite the metadata of historical items. After troubleshooting, I identified the issue with setData, so I simplified the reproduction steps.

The original command
[Command]
Name=Record source data
Command="
    copyq:
    const MIME_TAGS = 'application/x-copyq-tags';

    const getClipStr = (mime, limit) => {
      const bytes = clipboard(mime);
      if (!bytes?.length) return null;
      // Only read the header
      return str(limit && bytes.length > limit ? bytes.mid(0, limit) : bytes);
    };

    const getDataStr = (mime) => {
      const val = data(mime);
      return val?.length ? str(val) : null;
    };

    const handlers = [
      {
        rawMime: 'CopyQ/copy-time',
        extract: () => dateString('yy-MM-dd hh:mm:ss'),
        tag: (time) => time,
      },
      {
        rawMime: 'CopyQ/source-url',
        extract: () => {
          return (
            // Remove trailing special characters
            getClipStr('text/x-moz-url-priv')?.slice(0, -1) ??
            getClipStr('HTML Format', 2000)
              ?.match(/^SourceURL:\\s*([^\\r\\n]+)/m)?.[1]
              ?.trim()
          );
        },
        tag: (urlStr) => new URL(urlStr).hostname,
      },
      {
        rawMime: 'CopyQ/window-title',
        extract: () => getDataStr(mimeWindowTitle),
        tag: (text) => text?.trim(),
      },
    ];

    {
      const newTags = [];

      for (const handler of handlers) {
        const content = handler.extract();
        if (!content) continue;

        if (handler.rawMime) {
          setData(handler.rawMime, content);
        }

        // Only add when the tag function exists and returns a valid value
        const tagVal = handler.tag?.(content);
        if (tagVal) {
          newTags.push(String(tagVal));
        }
      }

      if (newTags.length > 0) {
        // Prevent the original data from containing commas used as separator tags
        const safeTags = newTags.map((t) => t.replace(/,/g, ','));
        setData(MIME_TAGS, safeTags.join(', '));
      }
    }"
Automatic=true
Enable=false
Icon=

Activity

  1. Elflare commented on Dec 10, 2025

    @Elflare

    I encountered the same bug.

  2. hluk commented on Jan 19, 2026

    @hluk
    Owner

    Copy the text once again

    If I understand the steps correctly, this is expected. The latest stored item is updated, if its text matches the new text in the system clipboard, otherwise a new item would be created. This is a workaround to avoid having multiple duplicate items in history, if new clipboard content only has some new formats compared to the previous content.

    I assume in this case, this is what happened:

    1. The system clipboard contains just "text/plain" format with "clipboard" text
    2. CopyQ stores new text item "clipboard"
    3. You add the Automatic command that adds a tag (new format)
    4. Copy the same text to the system clipboard again
    5. The automatic command adds the new format to the data to save (via setData(...))
    6. CopyQ sees that the new clipboard text is the same as the text of the latest item, so it updates the latest item

    Related code:

    // Also update previous item if the same selected text is copied to clipboard afterwards.
    if ( data.contains(mimeText) ) {
    const auto firstIndex = firstUnpinnedIndex();
    const QVariantMap previousData = firstIndex.data(contentType::data).toMap();
    if ( firstIndex.isValid()
    && previousData.contains(mimeText)
    // Don't update edited item.
    && (!isInternalEditorOpen() || currentIndex() != firstIndex) )
    {
    const auto newText = getTextData(data);
    const auto oldText = getTextData(previousData);
    if ( (mode == ClipboardMode::Clipboard)
    ? (newText == oldText)
    : (newText.startsWith(oldText) || newText.endsWith(oldText)) )
    {
    COPYQ_LOG("New item: Merging with top item");
    auto newData = previousData;
    for (auto it = data.constBegin(); it != data.constEnd(); ++it)
    newData.insert(it.key(), it.value());
    m.setData(firstIndex, newData, contentType::data);

  3. xymoryn commented on Jan 20, 2026

    @xymoryn
    Author

    If I understand the steps correctly, this is expected. The latest stored item is updated, if its text matches the new text in the system clipboard, otherwise a new item would be created. This is a workaround to avoid having multiple duplicate items in history, if new clipboard content only has some new formats compared to the previous content.

    You can check the video I uploaded. The text copied the first time is clipboard, while the text copied after enabling the automatic command is entries. However, new tags were added to both entries after the copy.

  4. hluk commented on Jan 21, 2026

    @hluk
    Owner

    You can check the video I uploaded. The text copied the first time is clipboard, while the text copied after enabling the automatic command is entries. However, new tags were added to both entries after the copy.

    Hmm, then I think the system clipboard "changed" again (maybe when focusing the other app?) even though it contained the same text. I don't think there is a good way to work around this.

    It overrides just the previous item in the list, right?

    I cannot reproduce the behavior myself, but I tested this only on Linux with Firefox browser.

    Related code (Windows-specific):

    const DWORD newClipboardSequenceNumber = GetClipboardSequenceNumber();

  5. xymoryn commented on Jan 21, 2026

    @xymoryn
    Author

    It overrides just the previous item in the list, right?

    Yes, each copy operation only updates the tags for the current item and the previous one, but the actual result is that the tags for all items other than the current one end up being incorrect 😂

    I'm not familiar with the underlying code logic, but after downgrading several versions, I found that 10.0.0 still updates tags correctly. Therefore, I suspect this issue was introduced in version 11.0.0.

  6. csy0x1 commented on Jan 31, 2026

    @csy0x1

    Same Here, I'm using the Preview Image Files script, in v11 and latest version, when I copy 2.png, it would show two 2.png preview, but the lower one is actually paste 1.png,

    Image Image Image Image
     [Command]
    Automatic=true
    Command="
        copyq:
        var height = 200
        var filePath = str(input())
        
        setData(mimeHtml,
          '<img height=\"' + height + '\" src=\"' + filePath + '\" />'
          // Show path.
          + '<div style=\"margin-top: 4px\">'
          + escapeHtml(data(mimeText))
          + '</div>'
        )"
    Icon=\xf03e
    Input=text/uri-list
    MatchCommand=copyq: new RegExp('^file:///.*\\.(jpg|png|gif|bmp)$').test(str(data(mimeUriList))) || fail()
    Name=Preview Image Files
    
  7. added a commit that references this issue on Jan 31, 2026
    d70f97e
  8. hluk commented on Jan 31, 2026

    @hluk
    Owner

    @xymoryn @csy0x1 Thanks for the clarification! It broke with the change in git commit 2eece2e.

    Fixing it in #3406.

  9. added a commit that references this issue on Jan 31, 2026
    53c8b86
  10. hluk commented on Jan 31, 2026

    @hluk
    Owner
  11. csy0x1 commented on Jan 31, 2026

    @csy0x1

    It works perfectly, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions