Skip to content

cargo-target-janitor: parse_fallback_scan, queue_claims and queue_task_states raise raw shape errors, not SafetyError — a malformed scan/row kills the janitor with no refused record (the #6812 invisible-death mode, second door) #6844

Closed

Description

@dezren39

Summary

infra/cargo-target-janitor/bin/cargo_target_janitor.py: the janitor's deletion-safety readers disagree about malformed input. The two gh-based readers convert shape errors to SafetyError — labelled_claimed_issues (:400, conversion at :427) and open_pr_branches (:486, conversion at :515) both catch (ValueError, KeyError, TypeError) and re-raise as SafetyError("invalid gh ... response"). The other three readers do not:

That matters because of how main ends (:963): it catches (SafetyError, OSError, CalledProcessError, TimeoutExpired, SystemExit), writes the refused run record to the state-dir log, and exits 2. The comment there records the #6812 lesson verbatim: a refusal is a run too — until it left a record, an hourly-dead janitor was visible only in the systemd journal. A malformed scan document or broker row raises AttributeError/KeyError/TypeError/ValueError instead, which is NOT in that tuple: the process dies with a raw traceback, no refused record is written, and the janitor is hourly-dead invisibly again — the exact failure mode #6812 fixed, through a different door. (The crash still happens before any deletion, so this is an observability/robustness defect, not a wrongful-deletion one.)

Evidence (verified this run, laptop @ origin/main 07ecb67)

Probe: ~/git/fleet/probe-janitor-shapes/probe.py (laptop), importing the real cargo_target_janitor.py from the laptop primary and driving the three readers directly. Control plus 10 malformed shapes:

  • control scan {"items":[{"number":123,"claim":{"state":"live"}}]} → {123} OK;
  • scan item is a string → AttributeError: 'str' object has no attribute 'get';
  • scan claim is a string → AttributeError (same);
  • scan item missing number → KeyError: 'number';
  • scan number is a dict → TypeError: int() argument ... not 'dict';
  • scan items is a dict / scan itself is a list → AttributeError;
  • queue_claims page is a PostgREST-style error dict → AttributeError: 'str' object has no attribute 'get' (the dict's keys are extended as rows);
  • queue_claims row id "abc" → ValueError: invalid literal for int();
  • queue_claims row payload is a string → AttributeError;
  • queue_task_states row missing id → KeyError: 'id'.

Every malformed case: SafetyError=False — none is the exception main records as a refusal.

Why it matters

The janitor is the only thing standing between the fleet worktree cargo targets and the disk-full failures in #6797/#3372, and its safety model is "fail closed AND leave a record". A shape change in the fleet queue fallback scan output (a binary that is actively evolving, #6658) or a broker error payload returned as a 200-shaped dict silently reverts the janitor to dying hourly with no state-dir record — the operator signal #6812 deliberately built disappears, while the systemd journal is the only witness, again.

Acceptance criteria

  • parse_fallback_scan, queue_claims, and queue_task_states validate the shapes they consume (scan is a dict with a list items; each item/claim is a dict; number/id are int-convertible; payload is a dict or null; page/rows are lists of dicts) and raise SafetyError naming the reader and the bad shape — mirroring the labelled_claimed_issues/open_pr_branches conversion pattern.
  • A malformed scan or broker response produces the refused run record and exit 2 from main, never a raw traceback.
  • Well-formed inputs behave byte-identically: the control scan, a normal claimed set, and normal task states return the same results as today.
  • Tests in infra/cargo-target-janitor/tests/test_cargo_target_janitor.py cover the malformed shapes above for all three readers plus the well-formed controls.
  • No change to what the janitor deletes, its caps, or its claim semantics — protection sets are computed exactly as before for valid inputs.

🤖 Filed by the fleet new-issue-creator (lane B) — verified with a probe against the real module this run; see the probe path in the evidence section.

Activity

  1. developing-today commented on Nov 7, 2026

    @developing-today
    Contributor
  2. added
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/small-fixQueue packet kind: bounded fix with reproduction and targeted tests
    priority/normalOrdinary priority (the default)
    app/cargo-target-janitorPackage/app: cargo-target-janitor (infra/ in this repo)
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    on Nov 7, 2026
  3. dezren39 commented on Nov 7, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

  4. dezren39 commented on Nov 8, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  5. added
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 8, 2026
  6. 20 remaining items

  7. removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    on Nov 8, 2026
  8. dezren39 commented on Nov 8, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up (resume)

  9. dezren39 commented on Nov 8, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked — CI pending on PR #7005

  10. added
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/claimedQueue worker lifecycle state
    on Nov 8, 2026
  11. dezren39 commented on Nov 8, 2026

    @dezren39
    MemberAuthor

    Fixed and merged. PR #7005 squash-merged as daa445170d2668183e05c0ab23fd34c934f61487.

    The issue is satisfied by this merge; closing it is left to the next reconciliation pass so the merge is visible on the timeline first.

  12. removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    work/assignedQueue worker lifecycle state
    on Nov 8, 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

    app/cargo-target-janitorPackage/app: cargo-target-janitor (infra/ in this repo)kind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator jobstatus:donetask/small-fixQueue packet kind: bounded fix with reproduction and targeted tests

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions