Apply agent-recorded actions to Bugzilla/Phabricator after a Hackbot run#6299
Open
suhaibmujahid wants to merge 13 commits into
Open
Apply agent-recorded actions to Bugzilla/Phabricator after a Hackbot run#6299suhaibmujahid wants to merge 13 commits into
suhaibmujahid wants to merge 13 commits into
Conversation
Adds a run_actions table (one row per recorded action, with status/result/ error/applied_at) and Run.finalized_at, the schema foundation a downstream apply step needs to record what it did to each action idempotently.
Agents could already record bugzilla.* actions, but nothing turned a recorded action back into a real API call. Adds that apply side: an ActionHandler protocol + ApplyContext (handlers never touch storage directly, only the download_artifact the caller provides), a HANDLERS registry keyed by dotted action type, and Bugzilla REST implementations for update_bug/add_comment/ add_attachment/create_bug. Also adds an optional `ref` on recorded actions so a later action can reference an earlier one's apply-time result (consumed by the applier's placeholder resolution). Bugzilla is talked to directly via requests rather than bugbug.bugzilla/libmozdata, whose bulk futures-based read pipelines don't fit a single-action write.
Records a phabricator.submit_patch action and, at apply time, submits it to Phabricator with no local repo, checkout, or moz-phab CLI needed. The submission payload is built during the agent's own run (build_phabricator_diff in changes.py, wired into publish_changes), reusing moz-phab's own diff-building code (mozphab.git/mozphab.diff, imported as a library via the new [phabricator] extra) against the checkout the agent already has. A synthetic commit squashes base..HEAD so moz-phab's single-commit diff works over the whole change; it carries an explicit git identity so it succeeds even in a hardened container. The result is published as the changes/phabricator_diff.json artifact. The apply-side handler is then a thin relay: download that artifact and make two Conduit calls (differential.creatediff + differential.revision.edit), create vs update keyed on whether an explicit revision_id was recorded. repositoryPHID is resolved apply-side since it's environment-specific. Nothing on the apply path imports git or moz-phab.
Replaces client-poll-only completion detection (GET /runs/{run_id} was the
only thing that ran _reconcile) with a push-based pipeline of two internal
event routes, named by role rather than by mechanism:
- /internal/events/agent-run-finished — the platform-completion ingress.
Fed by an Eventarc trigger on Cloud Run Jobs execution state today; the
name and downstream stay valid for other execution platforms later, only
the payload parsing (_cloud_run_execution_name) is platform-specific. It
calls finalize_run, which persists the terminal Run row idempotently
(Run.finalized_at) and publishes a run.completed event to the agent-run-
events Pub/Sub topic. GET /runs/{run_id} becomes a plain read.
- /internal/events/apply-run-actions — a consumer of run.completed, named
for its job since the event will feed other consumers later. Applies the
run's recorded actions via hackbot_runtime's handler registry, one
run_actions row per action, idempotent on retry, with
{{actions.<ref>.<field>}} placeholder resolution. Only succeeded runs'
actions are applied (also enforced by the subscription filter);
run.completed still fires for all terminal statuses so future consumers
(e.g. failure notifications) can react.
Events carry filterable attributes (event_type, agent, status, schema_version)
so consumers select via subscription filters; topics follow a per-domain
convention (<domain>-events, no hackbot- prefix in a hackbot-only project),
this being the agent-run domain. Both routes verify the OIDC push token
(require_push_auth), since the service is otherwise public. Fixes RunSummary
silently dropping the recorded actions array on the way in.
Auto-applying a run's recorded actions is now off by default and opt-in per
agent (`AgentSpec.auto_apply_actions`, default False). On a succeeded run the
actions are always recorded as run_actions rows so they're visible; they're
applied automatically only if the agent opted in, otherwise left pending.
Refactors the applier into ensure_action_rows + _apply_pending_rows, with
on_run_completed (record + conditional auto-apply, from the apply-run-actions
event) and apply_all_pending (manual). Adds GET /runs/{id}/actions and
POST /runs/{id}/actions/apply (apply-all, idempotent) so the UI can list and
apply pending actions, plus the RunActionDoc schema.
Adds an "Actions" section to the run detail view listing each recorded action with its apply status, plus an "Apply all pending actions" button (shown only when actions are pending) that calls the new apply endpoint and refreshes. Adds the RunAction type, listRunActions/applyRunActions client fns, and the /api/runs/[runId]/actions proxy route (GET + POST).
There was a problem hiding this comment.
Pull request overview
This PR adds an end-to-end “record actions during an agent run, then apply them to external systems after the run completes” flow, spanning hackbot-runtime (record/build artifacts), hackbot-api (finalize runs, publish completion events, record/apply actions), and hackbot-ui (display actions + manual apply).
Changes:
- Add
run_actionspersistence + apply logic in hackbot-api, triggered via internal Pub/Sub/Eventarc push routes and exposed via/runs/{run_id}/actions+/apply. - Add hackbot-runtime apply-side action handler infrastructure plus Bugzilla + Phabricator handlers, and generate a Phabricator diff artifact agent-side.
- Add hackbot-ui types, API proxy route, and Run detail UI to list actions and manually “apply all pending”.
Reviewed changes
Copilot reviewed 43 out of 44 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| uv.lock | Adds dependencies needed for Pub/Sub + mozphab/glean-related packages. |
| services/hackbot-ui/lib/types.ts | Adds RunAction / RunActionStatus types for UI consumption. |
| services/hackbot-ui/lib/hackbot.ts | Adds hackbot-api client calls for listing/applying run actions. |
| services/hackbot-ui/components/RunDetail.tsx | Fetches and renders run actions; adds “apply pending” UI. |
| services/hackbot-ui/app/api/runs/[runId]/actions/route.ts | Proxies list/apply actions requests to hackbot-api with auth gating. |
| services/hackbot-api/tests/test_run_actions_api.py | Tests the run-actions list/apply endpoints (handler-level). |
| services/hackbot-api/tests/test_pubsub.py | Tests Pub/Sub event attribute wiring for run.completed. |
| services/hackbot-api/tests/test_finalize_run.py | Tests new out-of-band finalize_run behavior + publishing. |
| services/hackbot-api/tests/test_events.py | Tests Pub/Sub push decoding and execution-name extraction. |
| services/hackbot-api/tests/test_actions_applier.py | Tests placeholder resolution + auto/manual apply gating. |
| services/hackbot-api/pyproject.toml | Adds Pub/Sub + google-auth + workspace dependency on hackbot-runtime. |
| services/hackbot-api/app/schemas.py | Adds RunSummary.actions and RunActionDoc. |
| services/hackbot-api/app/routers/runs.py | Adds run action endpoints; replaces request-driven reconcile with finalize_run; publishes run completion events. |
| services/hackbot-api/app/routers/events.py | Adds internal push endpoints for “run finished” + “apply actions”. |
| services/hackbot-api/app/routers/init.py | Exports events router. |
| services/hackbot-api/app/pubsub.py | Adds Pub/Sub publisher + event schema/attribute conventions. |
| services/hackbot-api/app/main.py | Registers the events router. |
| services/hackbot-api/app/gcs.py | Adds artifact byte download API for apply handlers. |
| services/hackbot-api/app/database/models.py | Adds finalized_at and RunAction model. |
| services/hackbot-api/app/config.py | Adds run-events topic + push auth settings. |
| services/hackbot-api/app/auth.py | Adds require_push_auth for push endpoints (OIDC verification). |
| services/hackbot-api/app/agents.py | Adds per-agent auto_apply_actions opt-in flag. |
| services/hackbot-api/app/actions_applier.py | Implements action row upsert, placeholder resolution, apply loop, and auto/manual apply entrypoints. |
| services/hackbot-api/alembic/versions/c1a2f3e4b5d6_run_actions_and_finalized_at.py | DB migration for finalized_at and run_actions. |
| libs/hackbot-runtime/tests/test_recorder.py | Tests ref recording behavior. |
| libs/hackbot-runtime/tests/test_phabricator_handler.py | Tests Phabricator apply handler logic (mocked Conduit). |
| libs/hackbot-runtime/tests/test_phabricator_actions.py | Tests recording-side phabricator.submit_patch. |
| libs/hackbot-runtime/tests/test_context.py | Tests publishing Phabricator diff artifact only when relevant action recorded. |
| libs/hackbot-runtime/tests/test_changes.py | Tests synthetic commit + Phabricator diff payload generation. |
| libs/hackbot-runtime/tests/test_bugzilla_handler.py | Tests Bugzilla apply handlers (mocked REST). |
| libs/hackbot-runtime/pyproject.toml | Adds phabricator extra pinned to MozPhab. |
| libs/hackbot-runtime/hackbot_runtime/context.py | Publishes Phabricator diff artifact when phabricator.submit_patch recorded; updates changes collection signature. |
| libs/hackbot-runtime/hackbot_runtime/changes.py | Adds synthetic commit + build_phabricator_diff; extends change metadata with repo_url. |
| libs/hackbot-runtime/hackbot_runtime/actions/recorder.py | Adds optional ref for cross-action result placeholders. |
| libs/hackbot-runtime/hackbot_runtime/actions/phabricator.py | Adds recording tool for Phabricator patch submission intent. |
| libs/hackbot-runtime/hackbot_runtime/actions/handlers/registry.py | Adds handler registry and get_handler dispatch. |
| libs/hackbot-runtime/hackbot_runtime/actions/handlers/phabricator_handler.py | Adds apply-side Phabricator submit handler (Conduit). |
| libs/hackbot-runtime/hackbot_runtime/actions/handlers/bugzilla_handler.py | Adds apply-side Bugzilla write handlers. |
| libs/hackbot-runtime/hackbot_runtime/actions/handlers/base.py | Adds shared ApplyContext + ActionResult contracts. |
| libs/hackbot-runtime/hackbot_runtime/actions/handlers/init.py | Exposes handler APIs. |
| libs/hackbot-runtime/hackbot_runtime/actions/claude_sdk.py | Adds Phabricator tools to the Claude SDK tool server. |
| libs/hackbot-runtime/hackbot_runtime/actions/init.py | Exports Phabricator actions module. |
| agents/bug-fix/pyproject.toml | Enables hackbot-runtime[phabricator] for the bug-fix agent image. |
| agents/bug-fix/hackbot_agents/bug_fix/config.py | Allows the agent to record phabricator.submit_patch actions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…on success The libs test job installed hackbot-runtime with only the claude-sdk extra, so mozphab was missing and build_phabricator_diff's real-diff test failed; add the phabricator extra so it runs. Also only set RunAction.applied_at when a handler actually succeeds, so a failed action isn't mistaken for an applied one.
…a diff moz-phab's git client reads user.email from the ambient git config (not the target repo's local config) and refuses to run without it, so build_phabricator_diff failed in environments without a global identity — CI, and agent containers in production. Point GIT_CONFIG_GLOBAL at a throwaway config carrying a hackbot identity for the duration of the moz-phab call.
The apply endpoint already re-attempts any action that isn't already applied (it skips only applied rows), so failed actions were retryable server-side but the button only appeared when actions were still pending. Show the button whenever there are pending or failed actions and label it to match (apply, retry, or both), and add a unit test locking the retry-failed / skip-applied contract of _apply_pending_rows.
Applying a run's Bugzilla actions one at a time sends a separate
PUT /bug/{id} per action, so an agent that both edits fields and adds a
comment on one bug triggers multiple bugmail notifications and shows as
several separate history entries. Bugzilla applies field changes plus a
comment in a single PUT as one transaction (one bugmail, one entry).
The applier now plans same-bug field changes to ride with the closest
comment in one combined PUT; any other comments on that bug still apply
separately (Bugzilla takes one comment object per request). Grouping lives
in the runtime lib (plan_coalesced_groups / merge_resolved); UpdateBugHandler
accepts an optional comment. The combined unit applies at the group's max idx
so backward {{actions.<ref>.<field>}} placeholders still resolve, and only
non-applied rows are grouped so retries never re-send an applied action.
Rename the env var the apply-side Bugzilla handlers authenticate with from BUGZILLA_TOKEN to BUGZILLA_API_KEY, matching that it holds a Bugzilla API key.
57530a8 to
ac64bf8
Compare
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.
No description provided.