fix(issue-fix): serialize continuous issue repair - #2006
Open
xielixing wants to merge 19 commits into
Open
Conversation
Adds a probe-gated bridge to the external `loopx` CLI's issue-fix capability,
which supplies a deterministic decision skeleton (which route to take for an
issue, how a PR's lifecycle projects forward) while performing no writes of its
own. BitFun keeps every side effect and supplies every piece of evidence.
Behind a non-default `loopx-issue-fix` feature, deliberately outside
`product-full` until the chain is verified against a real repository.
Two behaviors were found by testing against the real CLI rather than assumed:
- LoopX reports domain refusals as `{"ok": false, "error": ...}` on stdout *and*
exits nonzero. Parsing stdout before checking the exit status keeps the
structured reason instead of reporting a bare exit code.
- `--validation-label` is required for the `fix_pr` route. Without a named
validation surface LoopX downgrades to `triage_only` even when the context is
grounded, the issue reproduces, and the scope is bounded.
Sets `PYTHONUTF8=1` on every invocation: LoopX's 123 subprocess call sites pass
`text=True` without `encoding=`, so on a non-UTF-8 locale it decodes `gh` output
as the local codepage and dies. The env var fixes all of them at once and needs
no patch to LoopX.
Contract tests drive the real CLI and skip cleanly when it is absent, matching
the runtime probe gate.
Co-Authored-By: Claude <noreply@anthropic.com>
Adds `list_issues`, closing the one real backend gap for automatic issue fixing: the service could fetch a single issue by id but had no way to discover which issues exist. Five `list_pull_requests` implementations already existed; issues had no equivalent. Returns a new lightweight `ReviewPlatformIssueSummary` rather than the existing `ReviewPlatformIssueEvidence`, which carries a full body and every comment — enumerating a hundred issues must not pull all of that. Provider differences handled: - GitHub returns pull requests inline from its issues endpoint, marked only by a `pull_request` member, so they are filtered out. Continuation is inferred from a full page because `gh` surfaces no Link headers here; the check runs before PR filtering, since a page of only PRs can still be followed by issues. - GitLab addresses issues by project-scoped `iid`, not the global `id`, and has no "all" state literal — the filter is omitted entirely instead of sent empty. Continuation comes from its `x-next-page` header. Takes a request struct because the sibling `issue` method already sits at clippy's argument limit. Covered by mocked-HTTP tests for the GitLab path and an ignored test that drives the real `gh` CLI for GitHub, which mocks cannot reach. Co-Authored-By: Claude <noreply@anthropic.com>
Adds `RepositoryContextBuilder`, the evidence half of the integration. LoopX holds no code-reading ability and refuses to guess, so the quality of its route decisions depends entirely on what BitFun reports here. Every constraint LoopX's validator enforces is enforced at construction time instead, because a rejected payload costs a whole subprocess round trip: source-id shape and uniqueness, reference length, summary length measured after whitespace collapsing, the 16-source cap, advisory-only trust for memory and expert sources, and a pinned revision whenever a source claims current freshness. References must be repository-relative — an absolute local path would leak the operator's filesystem layout into a payload that can reach a public issue thread. `context_status` and `ungrounded_required_aspects` mirror LoopX's grounding rules locally, so a caller can decide what else to read without paying for a subprocess call. A contract test compares the prediction against the real CLI aspect by aspect, which is what would catch the two drifting apart. That comparison corrected a mistaken assumption of mine. I had assumed a partial context caps the route at `triage_only`; it does not. Two contract tests now pin the real behavior: a grounded context without `--validation-label` yields `triage_only`, while a merely partial context *with* the label yields `fix_pr`. LoopX distinguishes "which test files did you read" from "how will you check this fix", and only the latter gates opening a PR. Comments and the design doc that stated otherwise are corrected. Moves the module into a directory to hold the new submodule. Co-Authored-By: Claude <noreply@anthropic.com>
Runs one issue through feasibility → branch → PR lifecycle behind typed outcomes. The value is in the typing: LoopX's decisive fields sit at non-obvious paths, and reading them wrong is the failure mode that turns a refusal into an approval. Two paths were corrected by testing against the real CLI rather than assumed: `state` lives under `observation`, and `state_bucket` under `grouped_monitor_projection` — neither is top level, though the markdown rendering shows them flattened. A unit test asserting the wrong shape would have looked fine, so the mocked packets now match verified reality. Unrecognized routes and lifecycle decisions are errors, never defaults. Silently mapping an unknown value onto something permissive could open a pull request LoopX had declined. Optional evidence still degrades to empty, since guessing there is harmless. `plan_issue` skips branch preparation entirely on a non-fix route. Under `ExecutionMode::Execute` that would otherwise create a branch LoopX just refused to justify, so the skip is a safety property. `may_open_pull_request` requires the fix route, a ready review packet, and passing validation together. The feature ships with no runtime kill switch, so this gate lives on the action itself rather than relying on a disabled toggle. Co-Authored-By: Claude <noreply@anthropic.com>
Adds `review_platform_list_issues` across the three layers the existing `review_platform_get_issue` already spans: core facade wrapper, Tauri command, and typed frontend binding. The command takes an owned DTO because `ReviewPlatformListIssuesRequest` borrows its strings and cannot be deserialized directly. Co-Authored-By: Claude <noreply@anthropic.com>
Opens from a chat-header button into a right-panel tab: issue list on the left, selected issue's detail on the right. Follows the pull-requests button and `createReviewPlatformTab` for how the tab opens. Row state lives in `issueFixRunState` as pure functions, so the mapping from LoopX's decisions onto what a user sees is testable without rendering. The mapping that matters: a `user_gate` renders as blocked, never as done, and `nextIssueToRun` returns null while any row is blocked. Advancing past a gate would defeat the gate — that is the one behavior LoopX raises it for. Reason codes are shown verbatim rather than paraphrased, so a declined fix explains itself in LoopX's own vocabulary. The panel resolves `owner/repo`, host, and platform from the workspace's selected remote, since the header only knows the local checkout path. Platform is threaded through rather than hardcoded, so GitLab works too. The error status key is `stopped`, not `failed`: the i18n audit tracks `statuses.failed` as a shared term with a governance budget, and adding a 43rd duplicate would have needed that budget raised. Renaming was the honest fix rather than moving the baseline. Co-Authored-By: Claude <noreply@anthropic.com>
Multi-issue runs need continuation, budgets, and human gates. BitFun already owns all three in `thread_goal`, so this bridge adds none of its own — it only translates. That is also why nothing here reaches for a scheduler or quota: LoopX's issue-fix capability contributes neither, and its 35 modules import neither. The mapping that carries weight is `user_gate` → `Blocked`. `plan_serial_run` additionally returns no next issue while a gate is open, since handing one back would invite a caller to step over the gate rather than resolve it. `Blocked` stays resumable, so answering the question picks the run back up instead of stranding it. `is_resumable` duplicates the agent-runtime predicate rather than pulling in that crate for four lines. The test enumerates every `ThreadGoalStatus` variant, so a new one has to be classified deliberately instead of silently defaulting to non-resumable. Note on clippy: adding the runtime-ports dependency brings one pre-existing warning from that crate into this feature's build. It is not new code of mine. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Closes the gap my earlier summary glossed over: the panel could list and select issues, but nothing connected it to the orchestrator, so no fix action was reachable. A Start button now walks the selected issues serially through `feasibility`, driving the row states that were already implemented and tested. Planning only, verified rather than asserted: `ExecutionMode::Execute` has zero production callers, and the Tauri surface mentions execute exactly once — in the comment saying it has none. Nothing reachable from the UI can create a branch, run a command, or open a pull request. The run loop tracks state in a local variable rather than reading React state back each iteration, which would lag a render behind and could re-run an issue. It stops as soon as `nextIssueToRun` returns null, so an open gate halts the run instead of being stepped over. No repository context is generated yet, because nothing in BitFun generates one. LoopX therefore reports `not_provided` and declines to open a pull request. That is the honest current state, and its reason codes name exactly which evidence is missing — better than asserting a validation surface nobody checked. `loopx-issue-fix` is enabled for the desktop crate only, leaving `product-full` untouched so release builds of other consumers are unaffected. Co-Authored-By: Claude <noreply@anthropic.com>
…tion spawning - Add loopx-issue-fix to the services-integrations product-full feature group after real-repo validation against GCWing/BitFun GCWing#1849 - Register the feature and its optional dependency owners in the core boundary rules so the product-full assembly stays explicit - Wrap caller-declared validation commands with cmd /c on Windows: LoopX spawns them with subprocess.run(argv) and no shell, so .cmd shims such as pnpm fail with WinError 2 - Fix a pre-existing lib-test compile gap in plugin_source tests - Record the real-repo verification and the Windows defect in the integration design doc
The start button used to only project LoopX routes; a fix_pr route had no execution path, so no model was ever called. Wire the missing half: - New issue_fix_execute Tauri command: feasibility gate, then submit the fix task as a dialog turn to the session's agent loop (same scheduling path as a manual message, so the model's streaming output appears in the chat transcript) - Empty agent_type lets the coordinator resolve the session's own mode instead of overriding it - Panel: plan first, then submit fix_pr issues to the agent; non-fix routes record their reason codes and move on - Declare remote-workspace policies for all issue_fix commands and the pre-existing review_platform_list_issues gap - Document the execution model in the integration design doc
Author
|
Follow-up repair for the Issue-Fix gate/cancellation lifecycle:
Validation:
|
…host loop Replace the thread-goal bridge with a Kernel-owned autonomous loop: selected issues become LoopX intake todos, and BitFun's persistent cron service wakes one agent session every 10 minutes with an English host preamble plus the LoopX `heartbeat-prompt --compact` contract (thin mode depends on skill packs absent from BitFun sessions). - Project user gates from `todo list` instead of the quota preview, which is compacted to two entries; issue-linked gates win, unlinked gates still surface so an open gate can never stall the loop invisibly. - Add issue_fix_stop_autonomous (kill switch that also sweeps orphaned or duplicate jobs and survives a broken registry) and a quota-free issue_fix_autonomous_poll for the panel's 30s poll loop, since `quota should-run` appends a rollout event per call. - Serialize start/stop/answer-wake on HOST_LOOP_LOCK, self-heal duplicate cron jobs, refresh the heartbeat prompt snapshot at every gate answer, and surface host-loop failures (lastError/consecutiveFailures) in the UI. - Guard every panel state write with a monotonic ticket, pause polling during mutations, add a Stop button, re-project after failed gate answers, and collapse scheduled heartbeat turns into an expandable chip. - Prune ~60 dead issue-fix locale keys; keep en-US/zh-CN/zh-TW in parity. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
xielixing
force-pushed
the
codex/bitfun-loopx-issue-fix-repair
branch
from
August 4, 2026 11:53
bc5dc05 to
276583f
Compare
# Conflicts: # scripts/core-boundaries/rules/feature-rules.mjs # src/apps/desktop/Cargo.toml # src/crates/services/services-integrations/Cargo.toml # src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceItem.tsx # src/web-ui/src/flow_chat/components/modern/FlowChatHeader.tsx # src/web-ui/src/flow_chat/components/modern/UserMessageItem.tsx # src/web-ui/src/flow_chat/tool-cards/AskUserQuestionCard.tsx
During the first overnight full-batch run the agent hit cargo lock contention in a worktree, ran Stop-Process -Force against "stale cargo processes", and terminated the BitFun app hosting it (twice). The host preamble now carries a hard rule: never force-kill processes not started in the current turn; on lock contention wait, use a worktree-local CARGO_TARGET_DIR, or record a blocker todo. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GitHub's /repos/{o}/{r}/issues endpoint interleaves pull requests with
issues, and the client-side PR filter ran after taking one page — so once
the continuous Issue-Fix loop opened 19 PRs, the first page starved down
to a handful of issues and the panel showed a truncated queue with a zero
count. /search/issues with is:issue filters server-side, reports an exact
total_count, and keeps newest-first ordering explicitly.
Verified by the live github_issue_page_enumerates_a_public_repository
test (network + authenticated gh).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…block The heartbeat agent surfaces review/merge requests and authority gates as LoopX user todos, but the panel only rendered the single active gate card — merge-PR actions were visible only inside chat NOTIFY summaries. The panel now lists every open user_gate/user_action todo in a read-only "pending your action" block with provider jump links. No mutation surface is offered: gates answer through the existing question card, review/merge actions resolve on the provider side and close via the Kernel's monitors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The heartbeat agent wrote user-lane todo text free-form, so the pending
block ranged from terse ("Authorize merge of PR GCWing#2039") to a full drafted
comment pasted inline. The host preamble now prescribes one compact line:
action + PR/issue ref + which issue it serves + the state justifying it,
with long material routed to --note/--evidence. Display side: the primary
URL moves to the jump icon instead of repeating inline, and rows clamp to
two lines.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Gates and review requests were only visible inside the Issue-Fix panel (and chat NOTIFY text), so users working elsewhere missed them. The panel now raises app-level notification cards through the existing notification system when a new gate or user_action todo appears: warning card for gates, info cards for actions (digested into one card when a beat produces more than three), each with an "Open panel" action. The first projection seeds silently so pre-existing items do not re-toast on mount. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The continuous Issue-Fix host must work against any repository, but the hardening rules named BitFun's own toolchain (node/vite, pnpm, cargo, CARGO_TARGET_DIR). Restate them generically — never kill processes not started this turn, keep build outputs inside the worktree, remove the worktree and its caches at terminal closeout, one sibling worktrees folder per repository — and route repository-specific policy where LoopX says it belongs: the goal's active state and registry, read and written by the agent, never baked into the automation prompt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
queued,running,ok,error,cancelled) instead of treating an enabled schedule as active workFixes: N/A
Type and Areas
Type: Feature, regression fix, UI/UX, test, docs
Areas: Rust services integration, desktop/Tauri, web UI, worktree handling, LoopX integration
Motivation / Impact
The first integration could schedule a generic LoopX heartbeat that spent many turns reconciling control-plane state without selecting and repairing a concrete issue. It also projected an enabled heartbeat as if code repair were currently running. This change gives Issue Fix its own bounded execution contract, keeps multi-issue work serial and isolated, and preserves the authority gate before branch publication.
Verification
cargo test -p bitfun-services-integrations --features loopx-issue-fix autonomous::tests- 14 passedcargo test -p bitfun-services-integrations --features loopx-issue-fix absolute_references_are_rejected --lib- 1 passedcargo test -p bitfun-services-integrations --features git normalized_path_strips_extended_windows_prefix_for_git_cli --lib- 1 passedcargo test -p bitfun-core path_string_strips_extended_windows_prefix_for_ui_contracts --lib- 1 passedcargo check -p bitfun-desktop --lib- passedcargo test -p bitfun-desktop continuous_tests --lib- 3 passedpnpm --dir src/web-ui run type-check- passedpnpm run prepare:mobile-web- passedpnpm run desktop:dev- visible BitFun desktop window launched from this branch; web dev server listening on port 1422Reviewer Notes
Checklist