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-382 — delete_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
- 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"],
}
-
(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.
-
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.
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-prslash command. This is a natural language prompt — the agent interprets the instructions and callsgh 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 insova/git/merge.pyare 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 branchsova/git/merge.py:363-382—delete_remote_branch()exists but is not called in this pathsova/git/merge.py:154—_build_merge_args()correctly adds--delete-branchbut is only used by the programmatic path, not the dashboard2. 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_TRANSITIONSdoesn'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: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_TRANSITIONSonly 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_REVIEWfor reading ticket status_DEFAULT_TRANSITIONS(line 41-47) does NOT include"Code Review"for writing transitionsThe
jira_status_mappingconfig insova.tomlonly feeds into_resolve_state()(read path), not into_trigger_transition()(write path). Thejira_state_transitionsconfig 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()— buildstarget_namesfrom defaults + config overrideProposed 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:This ensures branch deletion happens at the Python level regardless of whether the Claude Code agent completed successfully.
JIRA transition defaults
_DEFAULT_TRANSITIONS:(Better long-term) Auto-derive reverse mappings: any status name in
_JIRA_STATUS_TO_STATEthat maps to a givenTaskStateshould automatically be tried as a transition name for that state. This keeps read and write sides in sync without manual duplication.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_transitionstosova.toml: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.