Skip to content

feat(roadmap): record the failure class and step of a stage item #4056

Open

Description

@dan-petty

Problem

Only the implement job has a failure class (roadmap/failure.py: FailureClass, ACTION_BY_CLASS, classify_failure, from #3065). classify_failure reads text and exit status. A stage, refine or intake error is a Python exception, not log text.

The stage item record holds the Python exception class name (stage.failed_error) and the time (stage.failed_at). _record_failures writes both. The record has no failure class and no step. So explain and inbox cannot show them, and a later issue cannot choose a retry time by class.

StageFailure (stage.py) carries the class name as a string. _failure_of (plan) and _write_failure (write) build it from the exception. The exception type is lost at that point.

FailureClass has 19 members. They already cover what the stage needs: provider, parser, plan_defect, ci_infra and head_moved. No new class is needed.

Expected

In roadmap/failure.py add two things, and use both in the same pull request:

  • StepName, a StrEnum with the steps that the stage records: plan and apply. Other issues add members when they use them.
  • CLASS_BY_EXCEPTION, one tuple of (exception type, FailureClass), read with isinstance, most specific first.

The table:

  1. RoadmapItemChangedError gives head_moved. It is a GitHubOperationError, so it comes first. It means that the item changed while the job worked, and the next round reads it again. It does not mean that a person must act.
  2. StructuredOutputValidationError gives parser.
  3. AIClientError gives provider.
  4. RoadmapStageError gives plan_defect.
  5. GitHubOperationError, GitOperationError, ToolExecutionError and any other DevOpsCLIError give ci_infra.

Add failure_class and step fields to StageFailure. _failure_of sets the step plan. _write_failure sets the step apply.

On the stage item record, add the keys stage.failed_step and stage.failed_class to StageRecordKey (stores/store.py). _record_failures writes them in the existing failure write, with no extra request. A staged item or an item stopped on the budget clears them, as it clears stage.failed_at today.

Update the table row of the stage keys in docs/ROADMAP_WORKFLOW.md (line 126).

Not in this issue:

Acceptance criteria

  • uv run pytest tests/roadmap/stage/test_roadmap_stage_failure_class.py - a provider error records the class provider and the step plan. A refused plan records plan_defect. A failed write records the step apply. An item-changed error records head_moved. A staged item clears the three keys.
  • uv run pytest tests/roadmap/test_roadmap_failure.py -k 'class_by_exception or step_name' - each mapped exception gives its class by isinstance. A subclass with no entry gives the class of its parent. The values of StepName are unique one-word strings.
  • grep -q 'stage.failed_class' docs/ROADMAP_WORKFLOW.md && uv run devops docs generate --check - the workflow document names the new keys and the generated documents are up to date (the grep part fails today).

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: failure.py, stage.py (_failure_of, _write_failure, _record_failures, StageFailure), stores/store.py (StageRecordKey), exceptions/roadmap.py and exceptions/git.py. I checked the exception tree: RoadmapItemChangedError is a GitHubOperationError, and StructuredOutputValidationError is an AIClientError. I did not run the code or the tests.

The consumers in this pull request are the writer of the two keys (_record_failures) and the tests. The reader is the issue #4060. RETRY_AFTER is not added here, because nothing would read it.

Existing tool considered

roadmap/failure.py (FailureClass, #3065) and plain isinstance. No library is needed.

Cost

Build: about 40k tokens and 2 h (one tuple, one enum, two record keys, 6 tests). Keep: about 2k tokens per quarter. Fix: about 8k tokens and 0.5 h.

Related

Part of #3027. #3258 (failure classes in the stage context; different), #3648 (explain and inbox; see the comment there), #3650, #3727. Order: before the issue #4060.

Activity

  1. added
    type/featureNew user-facing functionality or capability addition
    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/feature. The feat prefix of the title gives the type.
    • Scope: scope/roadmap. The (roadmap) scope of the title gives the scope.
    • Priority: P2-Medium. It enables later retry and reporting work but does not fix a current user-facing defect.
    • Value: Medium. It makes stage failures classifiable and step-aware, improving diagnostics for roadmap users.
    • Effort: Medium. It touches four files with well-specified changes and a clear acceptance test list.
    • Related: feat(roadmap): a failure taxonomy, and one action for each class #3065, which asks for a different change. Both work on the failure classification system; #3065 created the taxonomy for the implement job, this candidate adds step and class recording to stage items.
  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/featureNew user-facing functionality or capability additionvalue/mediumValue Medium

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions