Infrahub: Clearer Error For Deleted Branch In Diff Update
Introduction
This article addresses a bug in Infrahub version 1.5.1 related to the DIFF_UPDATE task. Specifically, when the target branch for a diff update has been deleted, the resulting error message is difficult to read and understand. This article explains the current behavior, the expected behavior, steps to reproduce the issue, and a proposed solution to improve error handling and provide more informative logging.
Current Behavior: Uninformative Error Messages
Currently, when the DIFF_UPDATE task is executed after the target branch has been deleted, the logs display an error that is hard to decipher. The traceback, as shown below, reveals a BranchNotFoundError, but it doesn't clearly indicate that the issue stems from the branch being deleted. This lack of clarity makes it difficult for users to quickly diagnose and resolve the problem.
2025-11-17T13:48:37.704073Z [error ] Encountered exception during execution: BranchNotFoundError('Branch: branch-s not found.') [prefect.flow_runs]
Traceback (most recent call last):
File "/Users/patrick/.virtualenvs/infrahub/lib/python3.12/site-packages/prefect/flow_engine.py", line 1371, in run_context
yield self
File "/Users/patrick/.virtualenvs/infrahub/lib/python3.12/site-packages/prefect/flow_engine.py", line 1433, in run_flow_async
await engine.call_flow_fn()
File "/Users/patrick/.virtualenvs/infrahub/lib/python3.12/site-packages/prefect/flow_engine.py", line 1385, in call_flow_fn
result = await call_with_parameters(self.flow.fn, self.parameters)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/patrick/Code/opsmill/infrahub/backend/infrahub/core/diff/tasks.py", line 31, in update_diff
await diff_coordinator.run_update(
File "/Users/patrick/Code/opsmill/infrahub/backend/infrahub/core/diff/coordinator.py", line 98, in run_update
await self.update_branch_diff(base_branch=base_branch, diff_branch=diff_branch)
File "/Users/patrick/Code/opsmill/infrahub/backend/infrahub/core/diff/coordinator.py", line 142, in update_branch_diff
refreshed_branch = await Branch.get_by_name(db=self.db, name=diff_branch.name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/patrick/Code/opsmill/infrahub/backend/infrahub/core/branch/models.py", line 152, in get_by_name
raise BranchNotFoundError(identifier=name)
infrahub.exceptions.BranchNotFoundError: Branch: branch-s not found.
This error originates from the update_diff function within the infrahub/core/diff/tasks.py file. The function attempts to retrieve the diff_branch using registry.get_branch. When the branch is not found (because it has been deleted), a BranchNotFoundError is raised, leading to the aforementioned traceback. The core of the problem lies in the lack of specific error handling for this scenario, resulting in a generic and unhelpful error message.
Expected Behavior: Clear Warning Message
The expected behavior is that the system should gracefully handle the BranchNotFoundError by catching it and logging a warning message. This warning should explicitly state that a diff update was requested for a branch that no longer exists. For example, the log message could read: "Warning: Diff update requested for branch '{branch_name}', but the branch has been deleted." This approach would provide users with immediate clarity regarding the cause of the issue, saving them time and effort in troubleshooting. This enhancement improves the user experience by providing actionable information directly in the logs.
Steps to Reproduce the Issue
To reproduce this issue, follow these steps:
-
Create a Branch: Create a new branch within Infrahub and add some initial content to it.
-
Shutdown Prefect Workers: Ensure that all Prefect workers are shut down to prevent any automatic processing of tasks.
-
Run the DIFF_UPDATE Mutation: Execute the following GraphQL mutation, replacing
$branchNamewith the name of the branch you created:mutation DIFF_UPDATE($branchName: String!) { DiffUpdate( data: {branch: $branchName} wait_until_completion: false ) { ok __typename } }This mutation triggers the
DIFF_UPDATEtask for the specified branch. Thewait_until_completion: falseargument ensures that the mutation doesn't wait for the task to complete, allowing you to proceed with the next step. -
Manually Delete the Branch: Directly delete the branch from the Infrahub database. This simulates the scenario where a branch is removed outside of the regular task execution flow. This step is crucial for triggering the
BranchNotFoundError. -
Startup Prefect Worker: Restart the Prefect worker. The worker will pick up the
DIFF_UPDATEtask and attempt to process it, leading to the error.
By following these steps, you can reliably recreate the error condition and observe the uninformative traceback in the logs. This allows for easier testing and validation of any proposed solutions.
Proposed Solution: Implement Error Handling
The proposed solution involves modifying the update_diff function in infrahub/core/diff/tasks.py to include specific error handling for the BranchNotFoundError. The following code snippet demonstrates the proposed change:
@flow(name="diff-update", flow_run_name="Update diff for branch {model.branch_name}")
async def update_diff(model: RequestDiffUpdate) -> None:
await add_tags(branches=[model.branch_name])
database = await get_database()
async with database.start_session(read_only=False) as db:
component_registry = get_component_registry()
base_branch = await registry.get_branch(db=db, branch=registry.default_branch)
try:
diff_branch = await registry.get_branch(db=db, branch=model.branch_name)
except BranchNotFoundError:
logging.warning(f"Diff update requested for branch '{model.branch_name}', but the branch has been deleted.")
return # Exit the task gracefully
diff_coordinator = await component_registry.get_component(DiffCoordinator, db=db, branch=diff_branch)
await diff_coordinator.run_update(
base_branch=base_branch,
diff_branch=diff_branch,
from_time=model.from_time,
to_time=model.to_time,
name=model.name,
)
This modification adds a try...except block around the registry.get_branch call. If a BranchNotFoundError is raised, the code now catches the exception, logs a warning message with a clear explanation of the issue, and then gracefully exits the task using return. This prevents the traceback from being displayed and provides a much more user-friendly experience. The warning message includes the branch name, making it easy to identify which branch caused the error.
Benefits of the Solution
Implementing this error handling mechanism offers several benefits:
- Improved User Experience: Users receive a clear and informative warning message instead of a confusing traceback.
- Faster Troubleshooting: The warning message directly indicates the cause of the problem, reducing the time required to diagnose and resolve the issue.
- Reduced Log Clutter: Prevents unnecessary tracebacks from cluttering the logs, making it easier to identify other potential issues.
- More Robust System: The system handles the case of deleted branches gracefully, preventing unexpected task failures.
Conclusion
By implementing the proposed solution, Infrahub can provide a more robust and user-friendly experience when dealing with diff updates for deleted branches. The addition of specific error handling and informative logging significantly improves the clarity and usability of the system. Addressing this issue enhances the overall reliability and maintainability of Infrahub. For more information on error handling best practices, refer to Exception Handling in Python.