Skip to content

fix(roadmap): write the item body and its marks in one request #4049

Open

Description

@dan-petty

Problem

Refine writes the result of an item with two requests. store.write_issue_body(...) comes first. Then set_field or set_marks follows (refine.py, _apply_refined). The job record is a block inside the issue body, so each call is one PATCH.

A stop between the two leaves the new body with the old marks. The next round reads this as a person's edit (WaitingItem(EDIT)) and refines the item again, with model spend.

apply_alternatives has the same order: write_issue_body, then set_marks, then comment. A stop after the body leaves an item with a new block and no checked_at. The item looks unchecked and is checked again.

A simple fix is not enough. Suppose refine wrote the body and the marks in one PATCH, and then the Status in a second request. A stop between them would leave refine.next_step=ready and a new refine.body_hash on an item whose Status is still New.

In _still_waiting (refine.py), NextStep.READY on a New item means that the stage job or a person returned it. The item would get a new look with model spend. Then RETURNED_BODY_HASH would make the second pass wait on a person. That moves the loss window and does not close it.

Expected

Put the body, the labels and the marks of one result in one PATCH.

  • GitHubRoadmapStore.set_field already writes the labels and the marks in one PATCH through _edit_issue.
  • Give _edit_issue, set_field and set_marks an optional argument body. When it is given, the request carries with_record(body, encode_job_record(held, changes)).
  • write_issue_body stays for repair.py, which has no marks.
  • Change the abstract methods (stores/store.py), the GitHub store, the dry-run request plan (request_plan.py) and the fake store (tests/support/fakes/roadmap_store.py).

Use it in _apply_refined for all three paths:

  • an item set Ready: set_field(..., body=new_body, marks=marks);
  • a Ready item set back to New: set_field(..., body=new_body, marks=marks);
  • an item that keeps its Status: set_marks(..., body=new_body).

Use it in apply_alternatives: set_marks(check.item, marks, body=check.new_body). A check changes no Status.

The comment of refine and of alternatives stays a separate request after the write. A stop before it loses a comment, not the state.

The request count of the normal path goes down by two for each refined item and each check: one read and one PATCH of write_issue_body.

Acceptance criteria

  • uv run pytest tests/roadmap/refine/test_roadmap_refine_one_write.py -k stop_after_first_write - the only write of an item is one request. A stop after it leaves the new body, the new marks and the new Status together. The item is not a WaitingItem(EDIT), and it is not a spurious stage return.
  • uv run pytest tests/roadmap/refine/test_roadmap_refine_one_write.py -k no_status_change - an item that keeps its Status is written with one request that holds the body and the marks.
  • uv run pytest tests/roadmap/test_roadmap_alternatives_one_write.py -k stop_after_write - a stop after the write leaves the new block and checked_at together.
  • uv run pytest tests/roadmap/test_roadmap_dry_runs.py -k body_with_marks - the dry-run plan lists one PATCH for the body with the marks.
  • uv run pytest tests/roadmap/stores/test_roadmap_store_body_with_marks.py -k set_marks_body - set_marks with body sends one PATCH with the new body and the record block.

Every command above fails on the unchanged code. The pull request cites the fail-to-pass run (devops ci fail-to-pass --base <ref>).

Feasibility

I read these files at origin/main:

  • refine.py: _apply_refined, _refine_marks, _still_waiting, _returned_wait.
  • alternatives.py: apply_alternatives.
  • stores/github_store.py: write_issue_body, set_field, set_marks, _edit_issue, _send_edit.
  • stores/store.py (line 917) and request_plan.py.

_edit_issue builds the body from issue.split() and the record block from encode_job_record(held, changes). A new body argument replaces the first part only. set_field reads the issue before the write and refuses a field that someone changed. That check stays.

I did not run the code or the tests. A first plan wrote the body and the marks in one PATCH and the Status in a second request. A check showed the fault described above. This plan replaces it.

Existing tool considered

with_record and encode_job_record of the store. No library is needed.

Cost

Build: about 60k tokens and 3 h. Keep: about 2k tokens per quarter. Fix: about 12k tokens and 1 h.

Related

Part of #3027. Order: after the issue #4044 (alternatives.py) and the issues that change store.py before it.

Activity

  1. added
    type/bugDefect, regression, or incorrect runtime behavior
    scope/roadmapThe roadmap jobs: intake, refine, stage, implement and the Service that runs them
    status/newStatus New: the item is not ready yet
    on Nov 7, 2026
  2. dan-petty commented on Nov 7, 2026

    @dan-petty
    OwnerAuthor

    Intake placed this item to the backlog: intake puts every new item in the backlog. A person plans the milestones.

    • Type: type/bug. The fix prefix of the title gives the type.
    • Scope: scope/roadmap. The (roadmap) scope of the title gives the scope.
    • Priority: P2-Medium. A service stop between two writes leaves inconsistent state and triggers wasted model calls, but only during a stop event.
    • Value: Medium. It prevents spurious re-refine cycles and wasted model spend after a service stop.
    • Effort: Medium. It changes the abstract store, GitHub store, dry-run plan, fake store, refine, and alternatives, with new tests.
    • Related: fix(roadmap): write each stage and alternatives item when done #4034, which asks for a different change. Both touch the write path for alternatives and stage, but #4034 writes items one at a time as done while the candidate merges body and marks into one PATCH per item.
  3. added
    priority/p2-mediumStandard priority feature work and architectural improvements
    on Nov 7, 2026
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

    effort/mediumEffort Mediumpriority/p2-mediumStandard priority feature work and architectural improvementsscope/roadmapThe roadmap jobs: intake, refine, stage, implement and the Service that runs themsource/agentFiled by an agentstatus/newStatus New: the item is not ready yettype/bugDefect, regression, or incorrect runtime behaviorvalue/mediumValue Medium

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions