Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions backend/src/baserow/contrib/automation/nodes/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,12 +424,27 @@ def dispatch_node(
logger.error(str(e))
return None

node = self.get_node(node_id)
simulate_until_node = (
node.workflow.get_graph().get_node(workflow_history.simulate_until_node_id)
if workflow_history.simulate_until_node_id
else None
error = (
"Node with ID {} was not found. The node was likely "
"deleted before the task was executed."
)
try:
node = self.get_node(node_id)
except AutomationNodeDoesNotExist:
logger.warning(error.format(node_id))
return None

try:
simulate_until_node = (
node.workflow.get_graph().get_node(
workflow_history.simulate_until_node_id
)
if workflow_history.simulate_until_node_id
else None
)
except AutomationNodeDoesNotExist:
logger.warning(error.format(workflow_history.simulate_until_node_id))
return None

if simulate_until_node:
allowed_nodes = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1456,3 +1456,29 @@ def test_dispatch_node_iterator_with_no_rows(data_fixture):
# Ensure we never return an empty chain, which would cause
# self.replace() to crash with an error.
assert result is None


@pytest.mark.django_db
@patch(f"{NODE_HANDLER_PATH}.logger")
def test_dispatch_node_with_deleted_node(mock_logger, data_fixture):
"""
In the rare case where a node is deleted between the time a dispatch
is queued and when the task actually runs, we should handle this
gracefully instead of crashing.
"""

data = create_workflow(data_fixture)
action_node = data["action_node"]
history = data["workflow_history"]

# delete the node to simulate a race condition
action_node_id = action_node.id
action_node.delete()

result = AutomationNodeHandler().dispatch_node(action_node_id, history.id)
assert result is None
expected_error = (
f"Node with ID {action_node_id} was not found. The node was likely "
"deleted before the task was executed."
)
mock_logger.warning.assert_called_once_with(expected_error)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"type": "bug",
"message": "Fix import workspace crashes on retry after failed backend import",
"issue_origin": "github",
"issue_number": 5140,
"domain": "core",
"bullet_points": [],
"created_at": "2026-04-07"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"type": "bug",
"message": "Fixed a bug that caused a crash due to a race condition that could happen if a node is deleted while it is being dispatched.",
"issue_origin": "github",
"issue_number": null,
"domain": "automation",
"bullet_points": [],
"created_at": "2026-03-26"
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,6 @@
/>
</div>

<ImportWorkspaceForm ref="form" @submitted="importWorkspace" />

<div
class="import-workspace__button-section"
:class="{
Expand All @@ -115,7 +113,7 @@
size="large"
:loading="importing"
:disabled="importing"
@click="submitForm"
@click="importWorkspace"
>
{{ $t('importWorkspaceModal.import') }}
</Button>
Expand All @@ -134,7 +132,6 @@ import { mimetype2icon } from '@baserow/modules/core/utils/fileTypeToIcon'
import job from '@baserow/modules/core/mixins/job'
import modal from '@baserow/modules/core/mixins/modal'
import error from '@baserow/modules/core/mixins/error'
import ImportWorkspaceForm from '@baserow/modules/core/components/import/ImportWorkspaceForm.vue'
import { notifyIf } from '@baserow/modules/core/utils/error'
import { ImportApplicationsJobType } from '@baserow/modules/core/jobTypes'
import { ResponseErrorMessage } from '@baserow/modules/core/plugins/clientHandler'
Expand All @@ -161,7 +158,6 @@ export default {
components: {
UploadFileDropzone,
SelectedFileDetails,
ImportWorkspaceForm,
ImportApplicationSelector,
},
mixins: [modal, error, job],
Expand Down Expand Up @@ -217,9 +213,6 @@ export default {
},
},
methods: {
submitForm() {
this.$refs.form.submit()
},
show(...args) {
this.hideError()
this.checkPendingImport()
Expand Down
Loading