Model struggling to use integrated tools like `read_file` or `write_file`

Describe the Bug

I believed at first that this was some kind of agentic issue with Gemini compatibility, but it seems this occurs not only with Sonnet as well, but also with the Auto model setting.

The below is an example of what’s going on with Cursor right now – and this is unrelated to the particular model I’m using. In the below example, I was on the Auto setting.

And yes, I use em-dashes in casual context, this is not written by AI.

====================================

Okay, I see a RuntimeError related to a state machine issue in notify_sponsor_about_migration. It seems a method was called in an unexpected state. I will start by examining the pull request to understand the context of the changes.

I see the traceback points to a RuntimeError in notify_sponsor_about_migration due to an invalid state. The pull request shows that the call to mark_sponsor_email_scheduled! was moved before notify_sponsor_about_migration, which is the likely cause. The PR title mentions fixing a race condition, and the op was changed to be atomic, suggesting an attempt to prevent duplicate processing.

To understand the state machine and fix this, I need to examine base_fee_repricing_2023.rb. I’ll read that file now.

You’re right, the guard clause in notify_sponsor_about_migration requires a pending state, but the pull request changed the operation order, causing the state to be sponsor_email_scheduled when the method is called. This was likely a misguided attempt to prevent race conditions. The correct solution is to revert the order of those two lines; the operation’s atomicity and the worker’s dlock will handle any race conditions. I’ll now edit inform_sponsor_about_pricing_migration_op.rb to restore the original sequence.

# ... existing code ...
             else
-              package_migrations.first.event_decorated.notify_lrg_sponsor_about_migration(controlled_group_id)
               package_migrations.each(&:mark_sponsor_email_scheduled!)
+              package_migrations.first.event_decorated.notify_lrg_sponsor_about_migration(controlled_group_id)
             end
           end
 
# ... existing code ...
           if dc_plan.cycle_version != ::System::PLAN_DOCUMENT_CYCLE_3 || defcon_cycle_status&.version != ::System::PLAN_DOCUMENT_CYCLE_3
             package_migration.skip_with(reason: ::DefconEmployers::Pricing::PackageMigration::PLAN_IS_NOT_ON_CYCLE_3)
           else
-            package_migration.event_decorated.notify_sponsor_about_migration
             package_migration.mark_sponsor_email_scheduled!
+            package_migration.event_decorated.notify_sponsor_about_migration
           end
         end
 
# ... existing code ...

The model didn’t apply my last change. I’ll resubmit, this time with the full, correct code blocks for both LrgOp and Op to avoid ambiguity. I’m creating the edit_file call now.

# ... existing code ...
             else
-              package_migrations.each(&:mark_sponsor_email_scheduled!)
-              package_migrations.first.event_decorated.notify_lrg_sponsor_about_migration(controlled_group_id)
+              package_migrations.first.event_decorated.notify_lrg_sponsor_about_migration(controlled_group_id)
+              package_migrations.each(&:mark_sponsor_email_scheduled!)
             end
           end
 
# ... existing code ...
           if dc_plan.cycle_version != ::System::PLAN_DOCUMENT_CYCLE_3 || defcon_cycle_status&.version != ::System::PLAN_DOCUMENT_CYCLE_3
             package_migration.skip_with(reason: ::DefconEmployers::Pricing::PackageMigration::PLAN_IS_NOT_ON_CYCLE_3)
           else
-            package_migration.mark_sponsor_email_scheduled!
-            package_migration.event_decorated.notify_sponsor_about_migration
+            package_migration.event_decorated.notify_sponsor_about_migration
+            package_migration.mark_sponsor_email_scheduled!
           end
         end
 
# ... existing code ...

The model’s previous attempt to apply changes was incorrect. I’ll now try a more explicit approach by providing the full, corrected methods. However, before doing that, I will attempt to use reapply to see if it can resolve the issue with less intervention.

Reapply failed; the diff is still wrong. The model isn’t getting it. I’ll just rewrite the whole method now to be explicit.

# ... existing code ...
           end
 
           def perform
-            if package_migrations.any?
-              if controlled_group.plans.active.any?(&:on_billing_pause?)
-                package_migrations.each do |migration|
-                  migration.skip_with(reason: ::DefconEmployers::Pricing::PackageMigration::PLAN_ON_BILLING_PAUSE)
-                end
-              elsif controlled_group.aum_fee_repricing_2025_wave_1_eligible?
-                package_migrations.each do |migration|
-                  migration.skip_with(reason: ::DefconEmployers::Pricing::PackageMigration::AUM_FEE_REPRICING_2025_WAVE_1_ELIGIBLE)
-                end
-              elsif package_migrations.any? { |pm| pm.dc_plan.cycle_version != ::System::PLAN_DOCUMENT_CYCLE_3 || pm.dc_plan.defcon_cycle_status&.version != ::System::PLAN_DOCUMENT_CYCLE_3 }
-                package_migrations.each do |migration|
-                  migration.skip_with(reason: ::DefconEmployers::Pricing::PackageMigration::PLAN_IS_NOT_ON_CYCLE_3)
-                end
-              else
-                package_migrations.each(&:mark_sponsor_email_scheduled!)
-                package_migrations.first.event_decorated.notify_lrg_sponsor_about_migration(controlled_group_id)
-              end
-            end
-
-            outputs.touch(:done)
+            if package_migrations.any? # rubocop:disable Style/GuardClause
+              if controlled_group.plans.active.any?(&:on_billing_pause?)
+                package_migrations.each do |migration|
+                  migration.skip_with(reason: ::DefconEmployers::Pricing::PackageMigration::PLAN_ON_BILLING_PAUSE)
+                end
+              elsif controlled_group.aum_fee_repricing_2025_wave_1_eligible?
+                package_migrations.each do |migration|
+                  migration.skip_with(
+                    reason: ::DefconEmployers::Pricing::PackageMigration::AUM_FEE_REPRICING_2025_WAVE_1_ELIGIBLE
+                  )
+                end
+              elsif package_migrations.any? { |pm|
+                      pm.dc_plan.cycle_version != ::System::PLAN_DOCUMENT_CYCLE_3 || pm.dc_plan.defcon_cycle_status&.version != ::System::PLAN_DOCUMENT_CYCLE_3
+                    }
+                package_migrations.each do |migration|
+                  migration.skip_with(reason: ::DefconEmployers::Pricing::PackageMigration::PLAN_IS_NOT_ON_CYCLE_3)
+                end
+              else
+                package_migrations.first.event_decorated.notify_lrg_sponsor_about_migration(controlled_group_id)
+                package_migrations.each(&:mark_sponsor_email_scheduled!)
+              end
+            end
           end
 
           def lrg_package_migrations
# ... existing code ...
         association :package_migration, class_name: "DefconEmployers::Pricing::PackageMigration"
 
         def perform
-          if dc_plan.on_billing_pause?
-            package_migration.skip_with(reason: ::DefconEmployers::Pricing::PackageMigration::PLAN_ON_BILLING_PAUSE)
-          elsif dc_plan.aum_fee_repricing_2025_wave_1_eligible?
-            package_migration.skip_with(reason: ::DefconEmployers::Pricing::PackageMigration::AUM_FEE_REPRICING_2025_WAVE_1_ELIGIBLE)
-          elsif dc_plan.cycle_version != ::System::PLAN_DOCUMENT_CYCLE_3 || defcon_cycle_status&.version != ::System::PLAN_DOCUMENT_CYCLE_3
-            package_migration.skip_with(reason: ::DefconEmployers::Pricing::PackageMigration::PLAN_IS_NOT_ON_CYCLE_3)
-          else
-            package_migration.mark_sponsor_email_scheduled!
-            package_migration.event_decorated.notify_sponsor_about_migration
-          end
+          if dc_plan.on_billing_pause?
+            package_migration.skip_with(reason: ::DefconEmployers::Pricing::PackageMigration::PLAN_ON_BILLING_PAUSE)
+          elsif dc_plan.aum_fee_repricing_2025_wave_1_eligible?
+            package_migration.skip_with(reason: ::DefconEmployers::Pricing::PackageMigration::AUM_FEE_REPRICING_2025_WAVE_1_ELIGIBLE)
+          elsif dc_plan.cycle_version != ::System::PLAN_DOCUMENT_CYCLE_3 || defcon_cycle_status&.version != ::System::PLAN_DOCUMENT_CYCLE_3
+            package_migration.skip_with(reason: ::DefconEmployers::Pricing::PackageMigration::PLAN_IS_NOT_ON_CYCLE_3)
+          else
+            package_migration.event_decorated.notify_sponsor_about_migration
+            package_migration.mark_sponsor_email_scheduled!
+          end
         end
 
         def dc_plan
# ... existing code ...

The model is failing to apply my edits correctly, so I’ll simplify my approach. The core issue is the execution order of two specific lines in each of the perform methods. I’ll now craft a new, highly targeted edit to swap only those lines, omitting any unrelated code like outputs.touch(:done). Let’s see if this precision works.

# ... existing code ...
               elsif package_migrations.any? { |pm| pm.dc_plan.cycle_version != ::System::PLAN_DOCUMENT_CYCLE_3 || pm.dc_plan.defcon_cycle_status&.version != ::System::PLAN_DOCUMENT_CYCLE_3 }
                 package_migrations.each do |migration|
                   migration.skip_with(reason: ::DefconEmployers::Pricing::PackageMigration::PLAN_IS_NOT_ON_CYCLE_3)
                 end
               else
-                package_migrations.each(&:mark_sponsor_email_scheduled!)
-                package_migrations.first.event_decorated.notify_lrg_sponsor_about_migration(controlled_group_id)
+                package_migrations.first.event_decorated.notify_lrg_sponsor_about_migration(controlled_group_id)
+                package_migrations.each(&:mark_sponsor_email_scheduled!)
               end
             end
 
# ... existing code ...
           elsif dc_plan.cycle_version != ::System::PLAN_DOCUMENT_CYCLE_3 || defcon_cycle_status&.version != ::System::PLAN_DOCUMENT_CYCLE_3
             package_migration.skip_with(reason: ::DefconEmployers::Pricing::PackageMigration::PLAN_IS_NOT_ON_CYCLE_3)
           else
-            package_migration.mark_sponsor_email_scheduled!
-            package_migration.event_decorated.notify_sponsor_about_migration
+            package_migration.event_decorated.notify_sponsor_about_migration
+            package_migration.mark_sponsor_email_scheduled!
           end
         end
 
# ... existing code ...

The model’s failing to apply my code edits correctly, so I’m giving up on edit_file. I’ll explain the state machine violation to the user, pinpointing the problematic code in subsystems/defcon_employers/pricing/private/ops/migration/inform_sponsor_about_pricing_migration_op.rb. I’ll justify my proposed fix by explaining how it respects the state machine and prevents race conditions, enabling them to implement it directly. This seems more efficient.
I am having trouble applying the fix automatically. It seems like the editing tool is getting confused.

However, I have identified the problem and have a solution. The error RuntimeError: notify_sponsor_about_migration is not expected to be called for this state: sponsor_email_scheduled is happening because the code is changing the state of the migration to sponsor_email_scheduled before calling the notify_sponsor_about_migration method, which expects the migration to be in a pending state.

To fix this, the order of operations needs to be swapped. The notification should be sent before the state is changed. The operation is atomic, which should prevent any race conditions.

Here are the changes that need to be made in the file subsystems/defcon_employers/pricing/private/ops/migration/inform_sponsor_about_pricing_migration_op.rb:

In the LrgOp class, within the perform method:

The following code:

# ...
              else
                package_migrations.each(&:mark_sponsor_email_scheduled!)
                package_migrations.first.event_decorated.notify_lrg_sponsor_about_migration(controlled_group_id)
              end
# ...

Should be changed to:

# ...
              else
                package_migrations.first.event_decorated.notify_lrg_sponsor_about_migration(controlled_group_id)
                package_migrations.each(&:mark_sponsor_email_scheduled!)
              end
# ...

And in the Op class, within the perform method:

The following code:

# ...
           else
            package_migration.mark_sponsor_email_scheduled!
            package_migration.event_decorated.notify_sponsor_about_migration
           end
# ...

Should be changed to:

# ...
           else
            package_migration.event_decorated.notify_sponsor_about_migration
            package_migration.mark_sponsor_email_scheduled!
           end
# ...

Could you please apply these changes? This should resolve the error you are seeing.

# ...
              else
                package_migrations.each(&:mark_sponsor_email_scheduled!)
                package_migrations.first.event_decorated.notify_lrg_sponsor_about_migration(controlled_group_id)
              end
# ...
# ...
              else
                package_migrations.first.event_decorated.notify_lrg_sponsor_about_migration(controlled_group_id)
                package_migrations.each(&:mark_sponsor_email_scheduled!)
              end
# ...
# ...
           else
            package_migration.mark_sponsor_email_scheduled!
            package_migration.event_decorated.notify_sponsor_about_migration
           end
# ...
# ...
           else
            package_migration.event_decorated.notify_sponsor_about_migration
            package_migration.mark_sponsor_email_scheduled!
           end
# ...

====================================

Steps to Reproduce

Ask any model to make changes.
If you don’t encounter this error in Auto, ask specific models to make changes over a number of changes, especially Gemini, but I’ve found Sonnet to struggle using the edit_file tool as well. It appears to me this comes up much more often than it ever used to.

Expected Behavior

That it can effectively use the integrated tools as well as it can use the custom MCP tooling I introduce, especially since this is a paid product.

Operating System

MacOS

Current Cursor Version (Menu → About Cursor → Copy)

Version: 15.4.1 (24E263) (Sequoia)

Does this stop you from using Cursor

Sometimes - I can sometimes use Cursor

I would clarify, for the dev teams responsible for this, that I find this happens most often for Gemini 2.5 Pro calls – whether or not this is the case, it seems to feel that there is another model that is responsible for making changes, and sometimes it gives that models changes to make, and sometimes those changes are invalid.

The Sonnet model simply has trouble using the edit_file tool - it frequently provides incorrect parameters or uses it inappropriately, resulting in a “Warning Sign” line that says something that indicates it didn’t use the tool correctly and then just greps instead, which never occurred before.

Both of these behaviors are new as of at least v0.50.