• graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • Real incident (2026-08-13, task b243dfe8), diagnosed live with the user after
    they asked "what's the actual bug behind this block": the task's benchmark
    regression-check node kept looping back to itself, then hit
    "exceeded max_attempts=3" -- but the DB had no record of WHY the final
    (3rd) attempt was rejected, only attempts 1 and 2's feedback. This made the
    block impossible to properly diagnose: reading attempt 3's actual content
    (now possible thanks to the artifact-promotion fix) showed it had correctly
    grounded itself in the task's own approved SDD, unlike attempts 1/2 -- but
    whether the judge agreed was unknowable from the DB alone.
    
    Root cause: _run_layers()'s exceeded-limit branch raised
    ConsistencyCheckFailedError immediately, skipping the
    _record_loop_back_event() call a few lines below entirely -- sig.feedback
    (the judge's real, specific reason for rejecting THIS FINAL attempt, the one
    that actually exhausted the budget) was silently discarded. Every other
    rejected attempt's feedback gets persisted via that same call; only the
    budget-exhausting one didn't.
    
    Fixed by recording it before raising, same as every other rejection. New
    test drives a real 3-attempt exhaustion (max_attempts=2 -> attempts 1, 2, 3)
    and asserts all three loop_back_triggered events exist with attempt 3's own
    distinct feedback text -- confirmed it reproduces the exact real gap (2
    events, not 3) on the pre-fix code.
    
    823/823 tests.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »

  • graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • Real incident (2026-08-13, task b243dfe8), diagnosed live with the user after
    they correctly challenged the architecture: "every node should have its own
    output, and node-to-node communication IS input/output flowing through
    edges -- why doesn't a node have an artifact?"
    
    They were right to push on this. work_node_execution.output_payload (the
    real Work Graph data-flow layer) was never the problem -- it's populated the
    instant any node completes, exactly as expected. The actual bug was one
    level up: _write_completed_node_artifacts's OLD `only_node_ids` parameter
    skipped any node not in "this specific append call's own newly-planned
    nodes", assuming "not new to this call" means "an earlier stage already
    promoted it". That assumption breaks the instant an earlier call's OWN
    promotion attempt never actually ran (e.g. it crashed even earlier in that
    same call, before reaching this function) -- which is exactly what happened
    to b243dfe8 across multiple retries today: brd/prd/sdd/benchmark nodes kept
    completing successfully ('done', real content), but zero artifact rows and
    zero .graphlemon/runs/<task_id>/ files were EVER created, going back to the
    task's original 2026-08-04 run. Every later append-mode retry kept treating
    those nodes as "not new, must already be handled" and excluded them forever.
    
    Renamed to `new_node_ids`, semantics inverted to check the real source of
    truth: a node whose artifact_type already has a genuine artifact row for
    this run_id is skipped UNLESS the node itself is newly-planned by this call
    (which correctly allows a retry-loop's superseding content to still promote
    a fresh artifact over a rejected one -- the scenario the previous attempt at
    this fix broke, caught by the existing
    test_rejected_first_attempt_replans_into_same_run_not_a_second_row test).
    
    New tests cover both real scenarios directly, plus a fix to
    test_orphaned_preexisting_run_with_no_artifacts_appends_not_collides's own
    setup (the orphaned node needs to be seeded as already-done, matching the
    real incident, not left for run_graph() to execute for real a second time).
    
    822/822 tests.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »
  • …3d96-main-checkout-drift
    graphlemon authored
     
    Browse Code »
  • User-reported gap: the alert banner only ever showed task.blocked_reason (a
    coarse category like 'execution_error') with zero indication of the actual
    cause -- the real message only existed in the matching task_status_changed
    execution_event's own detail text, with no path to the UI at all. Diagnosing
    today's several distinct real blockers required querying the DB directly each
    time, which the user correctly flagged as something the console itself should
    surface.
    
    task_query.get_task() now returns blocked_detail (the most recent blocking
    event's detail text) when status is blocked_pending_human. v_task_detail shows
    it as an expandable <details>/<summary> inside the alert banner (no JS wiring
    needed, matches the plain-vanilla-no-framework convention) so a long or
    multi-line real error message doesn't force itself into the banner's layout by
    default but is one click away in full, not truncated or hidden.
    
    Also investigated (not a bug, documented here for the record): two node logs
    the user found empty really are empty on disk -- execution_log_file rows exist
    but their target files were never created, for two nodes dating back to the
    task's original 2026-08-04 run, long before today's fixes. The log viewer's
    "暂无日志内容" is accurately reporting that state, not a rendering bug.
    
    820/820 tests, frontend smoke passes.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »
  • Real incident (2026-08-13, task b243dfe8): _normalize_graph_manifest() calls
    _validate_loop_back_targets() on the newly-planned manifest BEFORE it's merged
    with the existing persisted one (append mode). A new node's on_failure edge
    legitimately targeting an already-persisted node from an earlier append (e.g.
    a new verification node looping back to an earlier SDD node) was wrongly
    rejected as "does not exist in this graph_manifest" -- it genuinely doesn't
    exist in the brand-new manifest by itself, only once merged.
    
    _validate_loop_back_targets() gains additional_valid_target_ids, threaded from
    plan_work_graph()'s existing_manifest through _normalize_graph_manifest().
    Ancestor-chain validation is skipped for a target in this set -- an
    already-persisted node's ancestry was validated when it was originally
    planned.
    
    New unit test for the validation function directly, plus a real end-to-end
    test through plan_work_graph() in append mode.
    
    817/817 tests.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »
  • Real incident (2026-08-13, task b243dfe8): run_work_node() unconditionally
    INSERTed a new execution_log_file row every call. Fine the first time, but if
    the process crashed/restarted between that INSERT and execute_work_graph()
    genuinely returning, work_node_execution.status is left at 'running' (not
    'done', not a fresh node) and an execution_log_file row already exists for
    that node_exec_id. The next run_work_node() call for the SAME node_exec_id
    (REQ-06 requires this to be resumable) hit 'sqlite3.IntegrityError: UNIQUE
    constraint failed: execution_log_file.node_exec_id' and could never recover --
    this exact bug is what task 2084590a's own (still-blocked) goal describes.
    
    Reuses the existing row (log_file_id, file_path) instead of re-inserting, same
    "resume must reuse what already exists" pattern this function already applies
    to git_worktree_ref a few lines below. New test confirms the exact production
    traceback on the pre-fix code and passes post-fix.
    
    Also adds docs/todo/2026-08-13-blocked-pending-human-incident-retrospective.md,
    today's consolidated retrospective + fix/execution-order plan for the 6 tasks
    that were blocking on various independent real causes.
    
    815/815 tests.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »
  • Real incident (2026-08-13, task b243dfe8): _handle_task_route's auto-continue
    allowlist was ("queued", "planning_brd_prd_sdd", "coding", "validating").
    Org Graph unification Phase 1 (2026-08-08) added 'reviewing_planner_output' to
    task_execution._RESUMABLE_STATUSES, but nobody updated this separate allowlist
    to match -- the same exact bug the 2026-08-07 'validating' fix (see the
    existing comment right above this change) was supposed to have taught us to
    watch for, just one status later.
    
    Effect: manual_resume onto a task landing on 'reviewing_planner_output' flipped
    the DB status back and returned 200, but spawned no background thread. The task
    sat completely idle (zero new execution_event rows, no real subprocess) --
    which looks identical to "still stuck" from the console, even though the actual
    underlying bug that originally blocked it (a separate, already-fixed issue) was
    long gone. Diagnosed live: resumed b243dfe8, watched it sit with no activity
    for 7+ minutes, found zero events past the resume's own status-change row and
    no claude subprocess running anywhere.
    
    New regression test mirrors the existing 'validating' one exactly.
    
    814/814 tests.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »
  • Requested by user after today's incident retrospective: a manually-assignable
    execution-order hint across a project's tasks, shown as a new "执行顺序" column
    on v_task_board. Serial steps are whole numbers (1, 2, 3); parallel siblings of
    one step share the same integer part as decimals (2.1, 2.2). Purely advisory --
    nothing in the scheduler/dispatch loop reads it, it's a human/Boss-Agent-facing
    planning aid, not an enforcement mechanism.
    
    - schema.py: task.execution_priority REAL (nullable), migrate_add_task_execution_priority
      (plain ADD COLUMN, no CHECK constraint). Two earlier CHECK-rebuild migrations that
      also touch task (migrate_relax_org_node_constraints, migrate_add_reviewing_planner_
      output_status) now call it as a prerequisite and include the column in their own
      rebuilt task_new, same "depends on a later additive column" pattern
      migrate_relax_org_node_constraints already used for migrate_add_work_graph_fork_columns.
    - task_query.py: list_tasks/get_task expose it; new update_task_execution_priority
      (unlike update_task_goal_text, NOT gated to status='queued' -- it's a display hint,
      not a planning input, so it stays settable on in-flight/blocked tasks too).
    - backend_server.py: PUT /api/tasks/:id now accepts execution_priority independently
      of goal_text on the same endpoint.
    - frontend/app.js: new editable column on v_task_board, PUTs on change.
    
    Also adds docs/todo/append-run-node-id-collision-across-org-nodes-requirements.md,
    a third real finding from today's retrospective (task 911861fe: Coder and Validator's
    suggested node catalogs both converge on node_type="test_script", so their
    self-planned node_ids collide inside a shared merged work_graph_run -- PlanningFailedError,
    uncaught by _ROUTABLE_EXCEPTIONS, falls into the same generic execution_error bucket
    FR-7 already flagged).
    
    813/813 src tests, 134/134 supervisor tests, frontend smoke passes.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »
  • Real incident (2026-08-13, task 71868db6): ConcurrencyLimitReachedError isn't an
    error -- it just means this dispatch attempt found no free slot, which is normal,
    expected backpressure. Routing it through the same blocked_pending_human/
    execution_error bucket as a genuine failure was itself a bug: it forced a task
    that should just wait its turn into looking like it needs human judgment.
    
    _check_concurrency() raises before any task.status transition happens for this
    attempt, so task_execution.py::_mark_blocked() now leaves status untouched
    (still 'queued' for a fresh dispatch) instead of blocking -- the next
    new_goal_dispatch tick naturally retries it, no human/Boss Agent involvement
    needed. Still logged as a new 'concurrency_wait' execution_event (new
    migrate_add_concurrency_wait_event_type, same accumulated-CHECK-enum pattern as
    the existing event-type migrations) so the wait stays auditable, not silently
    invisible.
    
    This is the first concrete instance of FR-7 in
    docs/todo/boss-agent-autonomous-blocked-task-arbiter-requirements.md (system
    should emit distinguishable blocked_reasons/non-error states at the point of
    failure, not dump everything into one generic bucket).
    
    803/803 src tests, 134/134 supervisor tests.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • …a664-main-checkout-drift
    graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • …9856-main-checkout-drift
    graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • Two real findings from today's live self-hosting run, surfaced while diagnosing why
    bfd3ee82/71868db6 were blocked:
    
    1. trigger_org_node/trigger_org_node_structured use a fixed wall-clock
       subprocess.run(timeout=...) -- a long-running-but-genuinely-progressing agent call
       gets killed just for taking longer than a guessed constant (300s), which is exactly
       what happened to bfd3ee82's Validator planning call. Per an already-established
       (but never actually implemented) decision: an agent call should never be judged
       "stuck" by total duration alone, only by an extended period with no activity AND no
       error. New requirements doc:
       docs/todo/agent-call-liveness-not-fixed-duration-timeout-requirements.md.
    
    2. task_execution.py::_mark_blocked's catch-all dumps anything outside a small
       exception whitelist into blocked_reason='execution_error' -- including
       ConcurrencyLimitReachedError, which isn't an error at all, just normal backpressure.
       Boss Agent needs the system to emit distinct, meaningful reasons at the point of
       failure, not reverse-engineer root cause from a generic bucket -- added as FR-7 to
       the Boss Agent doc.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »
  • Requirements doc (BRD/PRD level) for a new org-level role that monitors
    blocked_pending_human tasks, does real diagnosis (not blind retry), and acts
    within an explicit authorization boundary instead of always waiting on a
    literal human -- generalizing the planner_reviewer precedent (an autonomous
    judgment agent already replacing a human review gate) to this surface.
    
    Motivated by today's real incident: 4 tasks blocked with the same generic
    execution_error reason but three genuinely different root causes (stale
    process, legacy narrative-output format, a real concurrency bug), all
    diagnosed by hand. Also documents watch_resume.py's real concurrency-race
    flaw as a requirement input (FR-6) so the eventual implementation doesn't
    repeat it.
    
    Deliberately does not pick the autonomy boundary per blocked_reason -- left
    as an open question for explicit user sign-off, not something Planner should
    infer silently.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »
  • v_task_detail's org-strip only ever covered planner/coder/validator -- no way to
    see planner_reviewer's verdict trail from the task detail page at all, only from
    v_graph_console. Rather than maintain two parallel, drifting implementations of
    "org node status for this task", v_task_detail now reuses the same 4-stage Org
    meta-graph component (renderOrgMetaGraph/mountOrgMetaGraph) v_graph_console
    already has.
    
    mountOrgMetaGraph/renderOrgMetaStageDetailHtml gain an optional enableNodeClick
    option so v_task_detail's usage gets real per-node live log-tail expansion
    (wired through the existing activeLogViewers/closeAllLogViewers/mountLogViewer
    machinery) while v_graph_console keeps its existing read-only snapshot
    behavior unchanged. ORG_NODE_DETAIL_RE now also accepts planner_reviewer so
    deep links into the 4th stage are reachable; landing on one auto-expands and
    scrolls to it (focusOrgMetaStage), matching the old focusOrgNodePanel's
    behavior. Dashboard's own 3-node org-strip is untouched.
    
    Deleted the now-dead renderTaskDetailOrgStripHtml/renderOrgComboPanelContent/
    wireOrgComboArtifactClicks/focusOrgNodePanel.
    
    node frontend/test_graph_console_smoke.js passes (extended with coverage for
    the regex widening and the enableNodeClick threading), independently
    re-verified.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »
  • Real prod incident (2026-08-13): two live tasks crashed with sqlite3.IntegrityError
    UNIQUE constraint failed: work_graph_run.task_id right after a planner_reviewer
    FAIL/INCONCLUSIVE verdict. ux_work_graph_run_active (just added this session) allows
    only one non-terminal work_graph_run row per task_id, but a rejected review's retry
    called plan_documents() with no run_id, which always INSERTs a fresh row -- the
    rejected attempt's own row is never marked done/failed just because its content was
    rejected (Phase 2c: one run spans a task's whole lifecycle).
    
    plan_documents() gains append_to_run_id, threaded into plan_work_graph()'s existing
    append machinery (same convention implement_code() already uses for Coder).
    _review_planner_output_with_retries() now carries prior_run_id across retry attempts
    so a re-plan appends into the still-open run instead of colliding with it. The first
    attempt (no prior run) is unaffected.
    
    New test drives a real FAIL-then-PASS retry through the actual plan_work_graph()
    INSERT path and the real ux_work_graph_run_active index -- confirmed it reproduces
    the exact production traceback on the pre-fix code and passes post-fix.
    
    801/801 src tests, 127/127 supervisor tests, both independently re-verified.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »
  • Real self-hosting DB (2026-08-13) has 10 task_ids with multiple non-terminal
    work_graph_run rows each (legacy per-org_node-row leftovers from before the
    merged-graph architecture) -- the migration's final CREATE UNIQUE INDEX
    ux_work_graph_run_active ON work_graph_run(task_id) crashed against this real
    data (IntegrityError), and because executescript() commits each DDL statement
    individually, the crash left the live DB in a partially-migrated state:
    org_node column dropped, but the index never created, with the old
    idempotency check (`if "org_node" not in cols: return`) then permanently
    skipping any further attempt to create it.
    
    Adds _reconcile_duplicate_active_work_graph_runs(), which marks all but the
    most-recently-created non-terminal run per task_id as 'failed' (not 'blocked'
    -- the index's WHERE clause treats 'blocked' as still-active, which was the
    first fix attempt and still failed) before the index is created. Recency is
    ranked by rowid, not run_id: real data has started_at=NULL on every legacy
    row, so a run_id tiebreak would pick among duplicates by UUID string value,
    unrelated to actual insertion order.
    
    Also splits the idempotency check into column-drop and index-creation halves
    so a database already in the partially-migrated state (like the live one)
    gets the missing index created on the next run instead of being skipped.
    
    Dry-run verified against a copy of the live 36-row production DB before
    applying to the real ~/.graphlemon/graphlemon.db (backend_server was down for
    this, no concurrent writers).
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »
  • …0cd7-main-checkout-drift
    graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »