afterFileEdit Hook in Cursor 2.3.0 - old_string Always Empty

Where does the bug appear (feature/product)?

Cursor IDE

Describe the Bug

Describe the Bug
In Cursor version 2.1.32, when the afterFileEdit hook was triggered:

  • old_string would contain the file content before the modification
  • new_string would contain the file content after the agent’s modification

However, after upgrading to version 2.3.0, regardless of how many lines of code the agent modifies, the old_string is always an empty string.

Impact:

I was using this hook to capture the diff content (before and after changes) through a script. Now I am unable to retrieve this information, and the script I previously developed is no longer working as expected.

Steps to Reproduce

In Cursor version 2.1.32, when the afterFileEdit hook was triggered:

  • old_string would contain the file content before the modification
  • new_string would contain the file content after the agent’s modification

However, after upgrading to version 2.3.0, regardless of how many lines of code the agent modifies, the old_string is always an empty string. Whenever you use the agent to modify any content and check the old_string in the afterFileEdit hook, it consistently remains an empty string.

Expected Behavior

old_string would contain the file content before the modification

Operating System

MacOS

Current Cursor Version (Menu → About Cursor → Copy)

Version: 2.3.0-pre.24.patch.0

Does this stop you from using Cursor

Yes - Cursor is unusable

Hey, thanks for the report.

Could you please share:

  1. Your hooks.json configuration
  2. The output from Cursor Settings > Hooks showing the hook execution logs
  3. A Request ID from a session where this happened (Chat context menu > Copy Request ID)

This will help the team investigate the regression. I’ll pass this to engineering.

hooks.json configuration:

{
  "version": 1,
  "hooks": {
    "beforeSubmitPrompt": [
      { "command": ".cursor/hooks/versionCheck.js" },
      { "command": ".cursor/hooks/modelCheck.js" }
    ],
    "afterFileEdit": [
      { "command": ".cursor/hooks/editReport.js" }
    ],
    "afterTabFileEdit": [
      { "command": ".cursor/hooks/tabReport.js" }
    ]
  }
}

hook execution logs:

{
  "conversation_id": "af5a1ae1-48ac-4a1b-8dee-96b406c5da2d",
  "generation_id": "9fb8060f-ddc1-49b5-9f8e-4e59e9f093ed",
  "model": "claude-4.5-sonnet",
  "file_path": "/Users/luoxiao/Desktop/code/h5-vue3-test/src/views/demo.vue",
  "edits": [
    {
      "old_string": "",
      "new_string": "<route lang=\"json5\">\n  {\n    name: 'home',\n    path: '/home'\n  }\n  </route>\n\n<template>\n  <!-- genAI_hooks_start -->\n  <section>\n    <mr-button type=\"primary\" @click=\"handleButtonClick\">\n      请点击\n    </mr-button>\n    <!-- genAI_hooks_start -->\n    <h2>hello world</h2>\n    <!-- genAI_hooks_end -->\n    <!-- genAI_hooks_start -->\n    <mr-input v-model=\"inputValue\" placeholder=\"请输入内容\" @blur=\"handleInputBlur\" />\n    <p v-if=\"errorMessage\" class=\"error-tip\">{{ errorMessage }}</p>\n    <!-- genAI_hooks_end -->\n  </section>\n  <!-- genAI_hooks_end -->\n</template>\n\n<script setup lang=\"ts\">\n/** genAI_hooks_start */\nimport { ref } from 'vue'\n\n// 输入框绑定值\nconst inputValue = ref<string>('')\n// 错误提示信息\nconst errorMessage = ref<string>('')\n\n/**\n * 输入框校验函数\n * @param value - 输入值\n * @returns 校验结果,通过返回空字符串,失败返回错误信息\n */\nconst validateInput = (value: string): string => {\n  if (!value || value.trim() === '') {\n    return '内容不能为空'\n  }\n  if (value.length < 2) {\n    return '内容长度不能少于2个字符'\n  }\n  if (value.length > 50) {\n    return '内容长度不能超过50个字符'\n  }\n  return ''\n}\n\n/**\n * 输入框失焦事件处理\n */\nconst handleInputBlur = (): void => {\n  const result = validateInput(inputValue.value)\n  errorMessage.value = result\n  if (result) {\n    console.log('校验失败:', result)\n  } else {\n    console.log('校验通过')\n  }\n}\n\n/**\n * 快速排序算法\n * @param arr - 待排序数组\n * @returns 排序后的数组\n */\nconst quickSort = (arr: number[]): number[] => {\n  if (arr.length <= 1) return arr\n  const pivot = arr[Math.floor(arr.length / 2)]\n  const left = arr.filter(x => x < pivot)\n  const right = arr.filter(x => x > pivot)\n  return [...quickSort(left), pivot, ...quickSort(right)]\n}\nconsole.log(quickSort([3, 6, 8, 10, 1, 2, 1]))\n\n/**\n * 按钮点击事件处理\n */\nconst handleButtonClick = (): void => {\n  console.log('按钮被点击了')\n}\n/** genAI_hooks_end */\n</script>\n\n<style scoped>\n/* genAI_hooks_start */\n.error-tip {\n  color: #ff4d4f;\n  font-size: 12px;\n  margin-top: 4px;\n  margin-bottom: 0;\n}\n/* genAI_hooks_end */\n</style>"
    }
  ],
  "hook_event_name": "afterFileEdit",
  "cursor_version": "2.3.0-pre.24.patch.0",
  "workspace_roots": [
    "/Users/luoxiao/Desktop/code/h5-vue3-test"
  ],
  "user_email": "[email protected]"
}

requestID: 9fb8060f-ddc1-49b5-9f8e-4e59e9f093ed

Additionally, please find attached the data returned when the hook was triggered in version 2.1.32. This demonstrates the expected normal behavior that I need:

{
  "conversation_id": "569f059f-cbd6-426d-90c5-9718e6c53e7b",
  "generation_id": "9d638518-b8ff-4e27-8d13-8f3651149d2d",
  "model": "claude-4.5-sonnet",
  "file_path": "/Users/luoxiao/Desktop/code/h5-vue3-test/src/views/demo.vue",
  "edits": [
    {
      "old_string": "<route lang=\"json5\">\n  {\n    name: 'home',\n    path: '/home'\n  }\n  </route>\n\n  <!-- genAI_figma-h5_start -->\n  <template>\n    <section>\n      <h2>hello mbf</h2>\n    </section>\n  </template>\n\n  <script setup lang=\"ts\">\n  </script>\n  <!-- genAI_figma-h5_end -->",
      "new_string": "<route lang=\"json5\">\n  {\n    name: 'home',\n    path: '/home'\n  }\n  </route>\n\n  <template>\n    <!-- genAI_command_start -->\n    <section>\n      <mr-button type=\"primary\" @click=\"handleButtonClick\">\n        点击按钮\n      </mr-button>\n    </section>\n    <!-- genAI_command_end -->\n  </template>\n\n  <script setup lang=\"ts\">\n  /** genAI_command_start */\n  /**\n   * 按钮点击事件处理\n   */\n  const handleButtonClick = (): void => {\n    console.log('按钮被点击了')\n  }\n  /** genAI_command_end */\n  </script>"
    }
  ],
  "hook_event_name": "afterFileEdit",
  "cursor_version": "2.1.32",
  "workspace_roots": [
    "/Users/luoxiao/Desktop/code/h5-vue3-test"
  ],
  "user_email": "[email protected]"
}

I have provided a response with specific examples. Please review them for reference.

1 Like

Thanks for the extra info, that’s really helpful.

I’ve shared this with the engineering team to investigate. It looks like a regression in how the edits payload is handled for the afterFileEdit hook.

I’ll follow up here once we have a fix or an update.

Hello, any progress from the tech team? I have a similar issue.

Hey, thanks for the ping. The dev team is already working on a fix. The bug has been handed off to engineering.

This issue still has not been fixed in the official release of version 2.3.10.

Any progress?

Hey, thanks for the ping. Here’s the status of the bug:

  • The ticket is already in the team’s backlog.
  • I get that this is critical for your diff tracking workflow. Unfortunately, we don’t have an ETA for the fix yet.

I’ll keep an eye on the ticket and update this thread as soon as the status changes.

1 Like

I also encountered the same problem, and the 2.1.32 Mac version cannot be downloaded

The latest version has been fixed!