{"id":936,"date":"2026-02-10T22:39:30","date_gmt":"2026-02-10T22:39:30","guid":{"rendered":"https:\/\/david.bookstaber.com\/?p=936"},"modified":"2026-02-10T22:39:33","modified_gmt":"2026-02-10T22:39:33","slug":"coding-agents-grow-up","status":"publish","type":"post","link":"https:\/\/david.bookstaber.com\/?p=936","title":{"rendered":"Coding Agents Grow Up"},"content":{"rendered":"\n<p>For years, programming something new followed a predictable, exhausting rhythm: write some code, hit a wall, and then disappear into a forest of documentation and StackOverflow tabs to find the trick to get it working.  In 2025, that era ended for me.<\/p>\n\n\n\n<p>Today, AI code assistants take so much drudgery out of development and debugging that the work has become mostly gratifying and rarely frustrating \u2014 the opposite of how programming was in the Before Times.<\/p>\n\n\n\n<p>Between my work and my side interests, I often feel like I <em>live <\/em>in Visual Studio Code (VSCode \u2014 a popular open-source development environment).  Early in 2025 I subscribed to GitHub Copilot, which integrates AI coding assistants into VSCode.  At $10\/month it&#8217;s a phenomenal bargain that offers software developers an easy way to loop in the latest models from OpenAI, Google, and Anthropic.  Now when I&#8217;m trying something new (like <a href=\"https:\/\/david.bookstaber.com\/?p=874\">this little project I did for fun<\/a>) I can mostly stay in VSCode and work with an AI assistant that has mastered all of the documentation.<\/p>\n\n\n\n<p>As <a href=\"https:\/\/david.bookstaber.com\/Interests\/2025\/07\/slant-ballistic-solutions-for-aerial-targets\/\">mentioned elsewhere<\/a>, my most significant side project in 2025 was helping Ukrainians develop an open-source ballistic calculator (<a href=\"https:\/\/github.com\/dbookstaber\/pyballistic\">pyballistic<\/a>) in Python and Cython.  I polished that off at the end of September.\u00a0 By that point I had begun to spend more time with Github Copilot, and the capabilities of its latest models gave me enough confidence and support to tackle what would have previously been an absurdly ambitious project for my day job: an Excel Real-Time Data (RTD) server for the Interactive Brokers API.\u00a0 (This RTD server feeds live market data, positions, and orders directly into Excel using native Excel formulas.)  After two months of working seven days a week on that, I had a beautiful piece of software so solid (and validated every build by over 800 unit tests) that I had begun to use it in live trading operations.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Early Childhood Development<\/h3>\n\n\n\n<p>Watching these models mature over the last year has been like watching a child grow up.<\/p>\n\n\n\n<p>Tell a child, &#8220;Clean your room.&#8221;  First they&#8217;ll spend more time arguing than it would take to just do it.  When they finally declare the task &#8220;done,&#8221; you might find a few toys picked up but most of the mess still there.  Emphasize that &#8220;clean your room&#8221; means <em>everything <\/em>and you might find the floor clean but everything shoved under the bed.  <\/p>\n\n\n\n<p>Claude v3 was notorious for hacking shortcuts.  Ask it to fix a failing test and it might just replace the test logic with a &#8220;return true;&#8221; statement.  Claude v3.5 wouldn&#8217;t be so brazen, but it was still prone to hack the example rather than the task.  GPT-4 and Gemini v2 would enthusiastically announce completion without checking their work.  Like the child who picks up two toys and concludes that his room must be clean, even though the mess is visible from outside the door.<\/p>\n\n\n\n<p>The teens came quickly: Claude v3.7 and its contemporaries would often spend more effort arguing that its failure was actually success than it would have taken to do the work correctly.<\/p>\n\n\n\n<p>More recent models have become more likely to keep checking and working until they succeed.  Performance of the latest models is still wildly variable: a model that astonishes me with its apparent skill one day may choke on something relatively simple the next.  But they are getting more consistent.  And they are definitely getting more intelligent.<\/p>\n\n\n\n<p><strong>What is intelligence?<\/strong>\u00a0 It becomes easy to see when you&#8217;re doing hard work with different models.  One of the neat things about Copilot is that you can choose to watch the model at work.  They all think &#8220;out loud,&#8221; meaning you can read their chain of thought to understand how and why they do things.  When it&#8217;s not having an off day, Claude Opus is <em>intelligent<\/em>.\u00a0 Given a problem:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>It can more reliably identify what matters.<\/li>\n\n\n\n<li>It has a better sense of what to look at and what to ignore.<\/li>\n\n\n\n<li>It produces better assessments of what\u2019s possible and makes better plans to get there.<\/li>\n\n\n\n<li>It knows when to persist and when to change directions.<\/li>\n<\/ul>\n\n\n\n<p>These are some of the things that separate a junior developer from a more experienced one.  They are also qualities that characterize <em>more intelligent <\/em>people.<\/p>\n\n\n\n<p><strong>Let me show you.<\/strong>  Have you ever wondered what it&#8217;s like debugging software?  Well, <em>debugging <\/em>is one thing the newer models can usually do as well as a good human programmer.  In fact, they can do it better because they can run the process faster and interact with the code more directly.  Below I pasted a transcript of Claude working to find and fix a tricky bug in my RTD server.  This could just as well have been a transcript of my thoughts if I had to debug it.  But whereas this would have been a draining hour+ distraction for me, the Claude instance cranked this out in minutes.<\/p>\n\n\n\n<!--more-->\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading has-text-align-center\">Transcript of Claude Debugging in VSCode<\/h2>\n\n\n\n<div class=\"wp-block-jetpack-markdown\"><p>\u2026 Now I see the issue. Let me check how the order list is populated &#8211; specifically the <code>GetOrAddOrderListTopic<\/code> and <code>HandleOpenOrderEnd<\/code> methods:<\/p>\n<p><em>Read file:OrderManager.cs, lines 165 to 225<\/em><\/p>\n<p>I see &#8211; when a new order list topic is created, it seeds with <code>BuildOrderListCsv()<\/code> only if <code>m_ordersSnapshotCompleted<\/code> is true. Let me check what <code>HandleOpenOrderEnd<\/code> does:<\/p>\n<p><em>Searched for text <code>HandleOpenOrderEnd<\/code>, 7 results.  Read file:OrderManager.cs, lines 410 to 490<\/em><\/p>\n<p>Now let me look at <code>PublishOrderListsIfAny<\/code> and <code>BuildOrderListCsv<\/code>:<\/p>\n<p><em>Read file:OrderManager.cs, lines 880 to 960<\/em><\/p>\n<p>Now I understand the flow. Let me check the test more closely to understand the potential race condition:<\/p>\n<p><em>Read OrderBehaviorTests.cs, lines 1795 to 1845<\/em><\/p>\n<p>I see a potential issue! The test subscribes to the order list AFTER calling <code>HandleOpenOrder<\/code>, but BEFORE calling <code>HandleOpenOrderEnd<\/code>. This is a problematic sequence because:<\/p>\n<ol>\n<li><code>HandleOpenOrder<\/code> adds orders to <code>m_orders<\/code><\/li>\n<li><code>GetOrAddOrderListTopic<\/code> is called &#8211; it sees <code>m_ordersSnapshotCompleted = false<\/code> and <code>m_ordersRequested = false<\/code>, so it seeds with empty string and calls <code>EnsureOrdersRequested()<\/code><\/li>\n<li><code>HandleOpenOrderEnd<\/code> sets <code>m_ordersSnapshotCompleted = true<\/code> and calls <code>PublishOrderListsIfAny()<\/code><\/li>\n<\/ol>\n<p>But wait &#8211; the issue is <code>m_ordersRequested<\/code>. Let me check:<\/p>\n<p><em>Searched for text <code>m_ordersRequested<\/code>, 6 results. Read OrderManager.cs, lines 193 to 223<\/em><\/p>\n<p>I see. When <code>GetOrAddOrderListTopic<\/code> is called:<\/p>\n<ul>\n<li><code>m_ordersSnapshotCompleted = false<\/code> (orders have been added but <code>HandleOpenOrderEnd<\/code> hasn\u2019t been called)<\/li>\n<li><code>m_ordersRequested = false<\/code> (we haven\u2019t called EnsureOrdersRequested yet)<\/li>\n<\/ul>\n<p>Since <code>!m_ordersRequested<\/code> is true, the condition <code>if (m_ordersSnapshotCompleted || !m_ordersRequested)<\/code> is TRUE, so it calls <code>BuildOrderListCsv()<\/code> immediately!<\/p>\n<p>So the test should work\u2026 Let me think about this more carefully.<\/p>\n<p>Actually, looking at the test, the issue is:<\/p>\n<ol>\n<li><code>HandleOpenOrder<\/code> is called 3 times for different orders<\/li>\n<li><code>GetOrAddOrderListTopic<\/code> is called &#8211; it seeds with <code>BuildOrderListCsv()<\/code> because <code>!m_ordersRequested<\/code> is true<\/li>\n<li><code>HandleOpenOrderEnd<\/code> is called &#8211; it calls <code>PublishOrderListsIfAny()<\/code> which updates the list again<\/li>\n<\/ol>\n<p>The problem is that <code>BuildOrderListCsv()<\/code> checks <code>ShowFilledOrders<\/code> which uses <code>m_showFilledOrdersOverride ?? ShowFilledOrdersConfig<\/code>. The test uses <code>showFilledOrdersOverride: false<\/code>, so that should be fine.<\/p>\n<p>Wait &#8211; the error message says the string is EMPTY! That means <code>BuildOrderListCsv<\/code> returned an empty string. This could happen if:<\/p>\n<ol>\n<li><code>m_orders<\/code> is empty (but we just added 3 orders)<\/li>\n<li>All orders are filtered out<\/li>\n<\/ol>\n<p>Let me look at <code>HandleOpenOrder<\/code>:<\/p>\n<p><em>Searched for text <code>public List&lt;int&gt; HandleOpenOrder<\/code>, 2 results. Read OrderManager.cs, lines 260 to 330<\/em><\/p>\n<p>I found it! Look at line 314-316:<\/p>\n<pre><code class=\"language-csharp\">\/\/ Mark list as changed only if this is a new order\nif (isNewOrder)\n{\n    \/\/ Update order list topics immediately (don't wait for next polling cycle)\n    updated.AddRange(PublishOrderListsIfAny());\n}\n<\/code><\/pre>\n<p>When <code>HandleOpenOrder<\/code> adds a NEW order, it calls <code>PublishOrderListsIfAny()<\/code>. But when the test calls <code>HandleOpenOrder<\/code> for the first time, there are NO order list topics yet (<code>m_orderListTopics<\/code> is empty), so <code>PublishOrderListsIfAny()<\/code> does nothing.<\/p>\n<p>Then when <code>GetOrAddOrderListTopic<\/code> is called, if <code>!m_ordersRequested<\/code> is true, it seeds with <code>BuildOrderListCsv()<\/code>. This should work\u2026<\/p>\n<p>But wait &#8211; the intermittent failure suggests a race condition or ordering issue. Let me re-read line 198:<\/p>\n<pre><code class=\"language-csharp\">if (m_ordersSnapshotCompleted || !m_ordersRequested)\n{\n    var csv = BuildOrderListCsv(descriptor);\n<\/code><\/pre>\n<p>So it seeds if EITHER:<\/p>\n<ol>\n<li><code>m_ordersSnapshotCompleted<\/code> is true (we\u2019ve finished the initial snapshot), OR<\/li>\n<li><code>!m_ordersRequested<\/code> is true (we haven\u2019t requested orders yet &#8211; implies we\u2019re using pre-existing data)<\/li>\n<\/ol>\n<p>The test sequence:<\/p>\n<ol>\n<li>Create OrderManager &#8211; <code>m_ordersRequested = false<\/code>, <code>m_ordersSnapshotCompleted = false<\/code><\/li>\n<li>Call <code>HandleOpenOrder<\/code> 3 times &#8211; orders are added to <code>m_orders<\/code><\/li>\n<li>Call <code>GetOrAddOrderListTopic<\/code> &#8211; <code>m_ordersSnapshotCompleted = false<\/code>, <code>m_ordersRequested = false<\/code>\n<ul>\n<li>Condition <code>!m_ordersRequested<\/code> is TRUE \u2192 calls <code>BuildOrderListCsv()<\/code> \u2192 should return \u201c12345\u201d (only open order)<\/li>\n<li>Then calls <code>EnsureOrdersRequested()<\/code> which\u2026 let me check what that does<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<p><em>Read OrderManager.cs, lines 775 to 835<\/em><\/p>\n<p>I see! <code>EnsureOrdersRequested()<\/code> sets <code>m_ordersRequested = true<\/code> and <code>m_currentPollCycleUtc = now<\/code>.<\/p>\n<p>So the sequence in the test:<\/p>\n<ol>\n<li>Create OrderManager &#8211; <code>m_ordersRequested = false<\/code>, <code>m_ordersSnapshotCompleted = false<\/code>, <code>m_currentPollCycleUtc = DateTime.MinValue<\/code><\/li>\n<li>Call <code>HandleOpenOrder<\/code> 3 times &#8211; orders added to <code>m_orders<\/code>, but <code>m_currentPollCycleUtc<\/code> is still <code>DateTime.MinValue<\/code> so orders are NOT marked as \u201cseen in poll\u201d<\/li>\n<li>Call <code>GetOrAddOrderListTopic<\/code>:\n<ul>\n<li>Condition <code>!m_ordersRequested<\/code> is TRUE \u2192 calls <code>BuildOrderListCsv()<\/code> \u2192 should return \u201c12345\u201d<\/li>\n<li>Then calls <code>EnsureOrdersRequested()<\/code> \u2192 sets <code>m_ordersRequested = true<\/code>, <code>m_currentPollCycleUtc = now<\/code><\/li>\n<\/ul>\n<\/li>\n<li>Call <code>HandleOpenOrderEnd<\/code>:\n<ul>\n<li>Sets <code>m_ordersSnapshotCompleted = true<\/code><\/li>\n<li>Check for disappeared orders: <code>if (m_currentPollCycleUtc &gt; DateTime.MinValue)<\/code> is TRUE (just set by EnsureOrdersRequested!)<\/li>\n<li>For each order, check <code>if (orderInfo.LastSeenInPollUtc &lt; m_currentPollCycleUtc)<\/code>\n<ul>\n<li>All orders have <code>LastSeenInPollUtc = FirstSeenUtc<\/code> (set in constructor to DateTime.UtcNow)<\/li>\n<li>But those were set BEFORE <code>m_currentPollCycleUtc<\/code> was set!<\/li>\n<\/ul>\n<\/li>\n<li>So ALL orders appear to have \u201cdisappeared\u201d and get marked as needing completion query<\/li>\n<li>The \u201cSubmitted\u201d order gets its status changed to\u2026 wait, let me check <code>MarkAsNeedsCompletionQuery<\/code><\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<p><em>Read OrderInfo.cs, lines 180 to 220<\/em><\/p>\n<p><em>Read OrderInfo.cs, lines 215 to 245<\/em><\/p>\n<p>Now I understand the race condition! Here\u2019s what happens:<\/p>\n<ol>\n<li>Orders are added via <code>HandleOpenOrder<\/code>. Their <code>LastSeenInPollUtc<\/code> is set to the time of creation.<\/li>\n<li>When <code>GetOrAddOrderListTopic<\/code> is called, it:\n<ul>\n<li>Seeds the list with <code>BuildOrderListCsv()<\/code> (returns \u201c12345\u201d correctly)<\/li>\n<li>Then calls <code>EnsureOrdersRequested()<\/code> which sets <code>m_currentPollCycleUtc = DateTime.UtcNow<\/code><\/li>\n<\/ul>\n<\/li>\n<li>When <code>HandleOpenOrderEnd<\/code> is called:\n<ul>\n<li>It sees <code>m_currentPollCycleUtc &gt; DateTime.MinValue<\/code> (true!)<\/li>\n<li>For the \u201cSubmitted\u201d order (permId 12345), it checks <code>orderInfo.LastSeenInPollUtc &lt; m_currentPollCycleUtc<\/code><\/li>\n<li>Due to timing, <code>LastSeenInPollUtc<\/code> (set during <code>HandleOpenOrder<\/code>) may be BEFORE <code>m_currentPollCycleUtc<\/code> (set during <code>EnsureOrdersRequested<\/code>)<\/li>\n<li>So the order appears to have \u201cdisappeared\u201d and <code>MarkAsNeedsCompletionQuery<\/code> is called<\/li>\n<li>Since <code>Remaining == 0<\/code> (not set in test), it sets <code>NeedsCompletionQuery = true<\/code><\/li>\n<li>Wait, actually the order has <code>Remaining &gt; 0<\/code> (100 shares, 0 filled), so it just sets <code>NeedsCompletionQuery = true<\/code> without changing status<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<p>Hmm, that doesn\u2019t change the status. But the test is checking if \u201c12345\u201d appears in the list after <code>HandleOpenOrderEnd<\/code>, and the assertion is failing with an empty string!<\/p>\n<p>Let me check what happens in <code>PublishOrderListsIfAny()<\/code>:<\/p>\n<p><em>Read OrderManager.cs, lines 865 to 895<\/em><\/p>\n<p>Let me think about this more carefully. The test:<\/p>\n<ol>\n<li>Seeds the list topic with <code>BuildOrderListCsv()<\/code> which returns \u201c12345\u201d<\/li>\n<li>Calls <code>HandleOpenOrderEnd()<\/code> which calls <code>PublishOrderListsIfAny()<\/code><\/li>\n<li><code>PublishOrderListsIfAny()<\/code> calls <code>BuildOrderListCsv()<\/code> again &#8211; this should still return \u201c12345\u201d<\/li>\n<\/ol>\n<p>Unless\u2026 there\u2019s something odd happening with the order processing. Let me look at the test constructor call again:<\/p>\n<pre><code class=\"language-csharp\">var order1 = new Order { OrderId = 12345, PermId = 12345, Account = &quot;TEST&quot;, Action = &quot;BUY&quot;, TotalQuantity = 100 };\norderManager.HandleOpenOrder(12345, contract, order1, new OrderState { Status = &quot;Submitted&quot; });\n<\/code><\/pre>\n<p>Wait &#8211; let me check if there\u2019s an issue with empty strings in the initial seeding. The test reads <code>listTopic.TopicValue?.ToString() ?? &quot;&quot;<\/code> AFTER calling <code>HandleOpenOrderEnd()<\/code>.<\/p>\n<p>Actually, I think I found the issue! Let me re-read the GetOrAddOrderListTopic flow:<\/p>\n<pre><code class=\"language-csharp\">var entry = m_orderListTopics.GetOrAdd(key, _ =&gt;\n{\n    wasAdded = true;\n    return new OrderListTopic(descriptor, topicId);\n});\n\ncreated = wasAdded;\n\nif (!wasAdded)\n{\n    entry.Topic.AddTopicId(topicId);\n}\nelse\n{\n    \/\/ Seed with current order list\n    if (m_ordersSnapshotCompleted || !m_ordersRequested)\n    {\n        var csv = BuildOrderListCsv(descriptor);\n        entry.Topic.SetTopicValue(csv);\n        entry.LastValue = csv;\n    }\n    else\n    {\n        entry.Topic.SetTopicValue(string.Empty);\n        entry.LastValue = string.Empty;\n    }\n    \n    EnsureOrdersRequested();\n}\n<\/code><\/pre>\n<p>If <code>m_ordersRequested<\/code> has been set to <code>true<\/code> by some other code path before this call, then the condition <code>!m_ordersRequested<\/code> is FALSE, and <code>m_ordersSnapshotCompleted<\/code> is also FALSE, so we go to the <code>else<\/code> branch and seed with empty string!<\/p>\n<p>But when would <code>m_ordersRequested<\/code> be set before the test calls <code>GetOrAddOrderListTopic<\/code>? Let me check if there\u2019s something in the test environment or if another test could affect this\u2026<\/p>\n<p>Actually, this is a state leakage issue between tests! If a previous test left <code>m_ordersRequested = true<\/code> somehow\u2026 but wait, each test creates a NEW OrderManager instance, so that\u2019s not it.<\/p>\n<p>Let me look for something else. Is there perhaps a Configuration issue? Let me check what happens with <code>ShowFilledOrdersConfig<\/code>:<\/p>\n<p><em>Read OrderManager.cs, lines 60 to 90<\/em><\/p>\n<p>The test uses <code>showFilledOrdersOverride: false<\/code> so that\u2019s not the issue.<\/p>\n<p>Let me check <code>IsTerminalStatus<\/code>:<\/p>\n<p><em>Searched for text <code>private static bool IsTerminalStatus<\/code>, 1 result. Read OrderManager.cs, lines 855 to 865<\/em><\/p>\n<p>OK, \u201cSubmitted\u201d is NOT a terminal status. Let me think about this\u2026<\/p>\n<p>Actually, I wonder if the flakiness is related to test parallelism. Let me check if there\u2019s anything that could be shared:<\/p>\n<p>Wait &#8211; I just realized something! The test reads <code>listTopic.TopicValue<\/code> which is set by <code>SetTopicValue<\/code>. But the test doesn\u2019t call anything that would UPDATE the topic value after the initial seeding.<\/p>\n<p>Actually no &#8211; <code>HandleOpenOrderEnd<\/code> calls <code>PublishOrderListsIfAny()<\/code> which should update it.<\/p>\n<p>Let me check if <code>GetOrAddOrderListTopic<\/code> returns the wrong topic for some reason. Actually, let me create a more detailed trace of what should happen:<\/p>\n<ol>\n<li><code>new OrderManager()<\/code> &#8211; fresh instance<\/li>\n<li><code>HandleOpenOrder(12345, contract, order1, new OrderState { Status = &quot;Submitted&quot; })<\/code> &#8211; order1 added<\/li>\n<li><code>HandleOpenOrder(12346, contract, order2, new OrderState { Status = &quot;Filled&quot; })<\/code> &#8211; order2 added<\/li>\n<li><code>HandleOpenOrder(12347, contract, order3, new OrderState { Status = &quot;Cancelled&quot; })<\/code> &#8211; order3 added<\/li>\n<li><code>GetOrAddOrderListTopic(...)<\/code> &#8211; creates new entry, seeds with <code>BuildOrderListCsv()<\/code> \u2192 \u201c12345\u201d<\/li>\n<li><code>HandleOpenOrderEnd()<\/code> &#8211; calls <code>PublishOrderListsIfAny()<\/code> which calls <code>BuildOrderListCsv()<\/code> \u2192 should still be \u201c12345\u201d<\/li>\n<\/ol>\n<p>The issue must be that <code>BuildOrderListCsv()<\/code> is returning empty string at some point. Let me look at <code>BuildOrderListCsv<\/code> again:<\/p>\n<pre><code class=\"language-csharp\">foreach (var kvp in m_orders)\n{\n    var permId = kvp.Key;\n    var orderInfo = kvp.Value;\n    \n    \/\/ Skip terminal orders unless ShowFilledOrders is enabled\n    if (!ShowFilledOrders &amp;&amp; IsTerminalStatus(orderInfo.Status))\n    {\n        continue;\n    }\n    ...\n    permIds.Add(permId);\n}\n<\/code><\/pre>\n<p>So the loop should add permId 12345 (Submitted = not terminal). Unless\u2026 wait, I wonder if there\u2019s an issue with <code>Configuration.GetCurrent()<\/code>.<\/p>\n<p>When running tests in parallel, if one test uses <code>Configuration.Override()<\/code> but doesn\u2019t properly dispose it, it could affect another test. But the override is <code>[ThreadStatic]<\/code>, so it should be per-thread\u2026<\/p>\n<p>Actually, looking at the test again, it uses <code>showFilledOrdersOverride: false<\/code> which is a constructor parameter, NOT a Configuration override. So it should work correctly.<\/p>\n<p>Let me check if there\u2019s something in <code>HandleOpenOrderEnd<\/code> that might reset the orders:<\/p>\n<p><em>Read OrderManager.cs, lines 415 to 495<\/em><\/p>\n<p>I see &#8211; <code>HandleOpenOrderEnd<\/code> calls <code>MarkAsNeedsCompletionQuery()<\/code> on orders that appear to have disappeared. But that doesn\u2019t change their Status unless Remaining==0.<\/p>\n<p>Actually wait &#8211; I just realized the issue! Look at lines 440-441:<\/p>\n<pre><code class=\"language-csharp\">if (orderInfo.LastSeenInPollUtc &lt; m_currentPollCycleUtc)\n<\/code><\/pre>\n<p>The orders have <code>LastSeenInPollUtc<\/code> set to their <code>FirstSeenUtc<\/code> (in the OrderInfo constructor). But <code>m_currentPollCycleUtc<\/code> is set by <code>EnsureOrdersRequested()<\/code> which is called AFTER the orders are created.<\/p>\n<p>Due to timing, <code>LastSeenInPollUtc<\/code> (time when HandleOpenOrder was called) could be BEFORE <code>m_currentPollCycleUtc<\/code> (time when EnsureOrdersRequested was called, milliseconds later).<\/p>\n<p>But this still doesn\u2019t explain the empty string! <code>MarkAsNeedsCompletionQuery<\/code> sets <code>NeedsCompletionQuery = true<\/code> but doesn\u2019t change the Status unless <code>Remaining == 0<\/code>.<\/p>\n<p>Let me look at what the test actually sets:<\/p>\n<pre><code class=\"language-csharp\">var order1 = new Order { OrderId = 12345, PermId = 12345, Account = &quot;TEST&quot;, Action = &quot;BUY&quot;, TotalQuantity = 100 };\norderManager.HandleOpenOrder(12345, contract, order1, new OrderState { Status = &quot;Submitted&quot; });\n<\/code><\/pre>\n<p>The Order has <code>TotalQuantity = 100<\/code> but no explicit <code>Filled<\/code> or <code>Remaining<\/code> values. Let me check what the OrderInfo constructor does with these:<\/p>\n<p>Looking at OrderInfo constructor:<\/p>\n<pre><code class=\"language-csharp\">public OrderInfo(int orderId, Contract contract, Order order, OrderState orderState)\n{\n    OrderId = orderId;\n    Contract = contract;\n    Order = order;\n    OrderState = orderState;\n    Status = orderState?.Status ?? string.Empty;\n    PermId = order?.PermId ?? 0;\n    FirstSeenUtc = DateTime.UtcNow;\n    LastUpdateUtc = FirstSeenUtc;\n    LastSeenInPollUtc = FirstSeenUtc;\n}\n<\/code><\/pre>\n<p>It doesn\u2019t set <code>Filled<\/code> or <code>Remaining<\/code> &#8211; those default to 0. So in <code>MarkAsNeedsCompletionQuery<\/code>:<\/p>\n<pre><code class=\"language-csharp\">if (Remaining == 0)\n{\n    Status = &quot;Filled&quot;;\n<\/code><\/pre>\n<p>This changes the Status from \u201cSubmitted\u201d to \u201cFilled\u201d!!! That\u2019s the bug!<\/p>\n<p>When the timing is such that <code>EnsureOrdersRequested<\/code> sets <code>m_currentPollCycleUtc<\/code> to a time AFTER the orders were created, the orders appear to have \u201cdisappeared\u201d and get marked. Since <code>Remaining == 0<\/code> (default), the Status is changed to \u201cFilled\u201d.<\/p>\n<p>Then <code>BuildOrderListCsv<\/code> skips it because <code>ShowFilledOrders = false<\/code> and <code>IsTerminalStatus(&quot;Filled&quot;) = true<\/code>.<\/p>\n<p>This is a race condition in the test setup. The fix is to ensure orders are marked as \u201cseen\u201d in the poll cycle BEFORE <code>HandleOpenOrderEnd<\/code> is called.<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>For years, programming something new followed a predictable, exhausting rhythm: write some code, hit a wall, and then disappear into&#8230;<\/p>\n","protected":false},"author":1,"featured_media":942,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"jetpack_post_was_ever_published":false},"categories":[36,14],"tags":[37,38],"class_list":["post-936","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-apps","category-technology","tag-ai-coding","tag-github-copilot","post-archive"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/david.bookstaber.com\/WP\/wp-content\/uploads\/2026\/02\/CodingAgentsGrowUp.jpg","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/david.bookstaber.com\/index.php?rest_route=\/wp\/v2\/posts\/936","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/david.bookstaber.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/david.bookstaber.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/david.bookstaber.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/david.bookstaber.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=936"}],"version-history":[{"count":6,"href":"https:\/\/david.bookstaber.com\/index.php?rest_route=\/wp\/v2\/posts\/936\/revisions"}],"predecessor-version":[{"id":943,"href":"https:\/\/david.bookstaber.com\/index.php?rest_route=\/wp\/v2\/posts\/936\/revisions\/943"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/david.bookstaber.com\/index.php?rest_route=\/wp\/v2\/media\/942"}],"wp:attachment":[{"href":"https:\/\/david.bookstaber.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=936"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/david.bookstaber.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=936"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/david.bookstaber.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=936"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}