Skip to content

Apply agent-recorded actions to Bugzilla/Phabricator after a Hackbot run#6299

Open
suhaibmujahid wants to merge 13 commits into
mozilla:masterfrom
suhaibmujahid:worktree-hackbot-post-run-actions
Open

Apply agent-recorded actions to Bugzilla/Phabricator after a Hackbot run#6299
suhaibmujahid wants to merge 13 commits into
mozilla:masterfrom
suhaibmujahid:worktree-hackbot-post-run-actions

Conversation

@suhaibmujahid

Copy link
Copy Markdown
Member

No description provided.

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).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_actions persistence + 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.

Comment thread services/hackbot-ui/components/RunDetail.tsx
Comment thread services/hackbot-api/app/actions_applier.py Outdated
Comment thread services/hackbot-api/app/routers/events.py
Comment thread services/hackbot-api/app/routers/events.py
…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.
@suhaibmujahid suhaibmujahid marked this pull request as ready for review July 10, 2026 02:28
@suhaibmujahid suhaibmujahid requested a review from a team as a code owner July 10, 2026 02:28
@suhaibmujahid suhaibmujahid force-pushed the worktree-hackbot-post-run-actions branch from 57530a8 to ac64bf8 Compare July 10, 2026 04:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants