Skip to content

Post-merge cleanup gaps: branch not deleted + JIRA transition name mismatch #633

Description

@dsova06

Problem

Two related issues surfaced during PR #745 integration on RedHatInsights/turnpike:

1. Remote branch not deleted after merge

When a PR is merged via the dashboard "Integrate" button, the remote feature branch is not deleted if the target GitHub repo has delete_branch_on_merge: false.

What happened: PR 745 was merged successfully, but the remote branch feat/RHCLOUD-49875-... remained on GitHub. The user had to click "Delete branch" manually in the GitHub UI.

Root cause: The dashboard "Integrate" button spawns a Claude Code agent that runs the /integrate-pr slash command. This is a natural language prompt — the agent interprets the instructions and calls gh pr merge --delete-branch. If the agent crashes, runs out of context, or otherwise fails after merging but before the branch deletion step completes, _wait_and_finalize() (agent_lifecycle.py:929-940) detects the merge succeeded and marks the run as "done" — but the branch was never deleted.

There is no Python-level safety net. The merge_pr() / delete_remote_branch() functions in sova/git/merge.py are never called during dashboard integrate — the entire merge flow is delegated to the Claude Code agent's interpretation of the prompt.

Relevant code:

  • sova/dashboard/services/agent_lifecycle.py:929-940 — detects "merge succeeded despite crash" but does not clean up the branch
  • sova/git/merge.py:363-382delete_remote_branch() exists but is not called in this path
  • sova/git/merge.py:154_build_merge_args() correctly adds --delete-branch but is only used by the programmatic path, not the dashboard

2. JIRA transition names don't match common board configurations

When sova tries to transition a JIRA ticket (e.g., to "In Review" after PR creation, or to "ON_QA" after merge), the transition fails because _DEFAULT_TRANSITIONS doesn't include all common JIRA workflow transition names.

What happened: After creating PRs for RHCLOUD-49874 and RHCLOUD-49875, sova tried to transition both tickets to in_review. The log shows:

"state": "in_review",
"available": ["New", "Refinement", "Backlog", "In Progress", "Code Review", "ON_DEV", "ON_QA", "Release Pending", "Closed"],
"event": "transition.no_match"

Sova tried "In Review" and "Review" (from _DEFAULT_TRANSITIONS), but the JIRA board uses "Code Review" — which is not in the default list.

The same issue would affect ON_QA: The available transition is "ON_QA" (all-caps, underscored), but _DEFAULT_TRANSITIONS only has ["On QA", "QA", "Verification", "Ready for QA"]. The exact string "ON_QA" is not matched.

Root cause: There's an asymmetry between read-side and write-side mappings:

  • _JIRA_STATUS_TO_STATE (line 28-39) correctly maps "Code Review" -> TaskState.IN_REVIEW for reading ticket status
  • _DEFAULT_TRANSITIONS (line 41-47) does NOT include "Code Review" for writing transitions

The jira_status_mapping config in sova.toml only feeds into _resolve_state() (read path), not into _trigger_transition() (write path). The jira_state_transitions config exists for write-side override but is not documented and is empty by default.

Relevant code:

  • sova/adapters/jira.py:28-39_JIRA_STATUS_TO_STATE (read-side, correct)
  • sova/adapters/jira.py:41-47_DEFAULT_TRANSITIONS (write-side, incomplete)
  • sova/adapters/jira.py:607-637_trigger_transition() — builds target_names from defaults + config override

Proposed Fix

Branch deletion safety net

Add a post-merge branch cleanup step in _wait_and_finalize() when it detects a successful merge for integrate-pr roles. After line 940:

# Safety net: delete remote branch if merge succeeded but agent may have
# crashed before the branch deletion step
try:
    from sova.git.merge import delete_remote_branch
    repo = _resolve_repo(agent.project_dir)  # needs implementation or existing helper
    branch = await _get_pr_head_branch(agent.pr_number, repo)
    await delete_remote_branch(branch, repo=repo, github_user=github_user)
except Exception:
    log.warning("finalize.branch_cleanup_failed", pr=agent.pr_number, exc_info=True)

This ensures branch deletion happens at the Python level regardless of whether the Claude Code agent completed successfully.

JIRA transition defaults

  1. Add common JIRA transition names to _DEFAULT_TRANSITIONS:
_DEFAULT_TRANSITIONS: dict[TaskState, list[str]] = {
    TaskState.BACKLOG: ["To Do", "Backlog", "Open"],
    TaskState.IN_PROGRESS: ["In Progress", "Start Progress"],
    TaskState.IN_REVIEW: ["In Review", "Review", "Code Review"],
    TaskState.ON_QA: ["On QA", "QA", "ON_QA", "Verification", "Ready for QA"],
    TaskState.DONE: ["Done", "Closed", "Resolved", "Close"],
}
  1. (Better long-term) Auto-derive reverse mappings: any status name in _JIRA_STATUS_TO_STATE that maps to a given TaskState should automatically be tried as a transition name for that state. This keeps read and write sides in sync without manual duplication.

  2. Consider case-insensitive matching in _trigger_transition() to handle variants like "ON_QA" vs "On QA".

Affected Projects

Any project using JIRA boards with non-standard transition names (e.g., "Code Review" instead of "In Review", "ON_QA" instead of "On QA"). Confirmed on RedHatInsights/turnpike.

Workaround

Add explicit jira_state_transitions to sova.toml:

[task_source]
jira_state_transitions = { "in_review" = "Code Review", "on_qa" = "ON_QA" }

This is checked first (jira.py:618-620) and would resolve the mismatch for individual projects, but every project with non-standard JIRA boards needs this workaround.

Triage Assessment

Title: Post-merge cleanup gaps: branch not deleted + JIRA transition name mismatch
Has description: yes
Suitability: ready
Confidence: 85%
Complexity: complex
Missing context: none
Labels: none

Issue has structured sections indicating clear scope; ready for research.

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent:readyReady for autonomous developmentagent:triagedIssue triaged by SOVA agent

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions