Skip to content

Delete instance on instantiation failiure#1714

Open
ivarne wants to merge 4 commits intomainfrom
bug/delete-instance-on-instantiation-failure
Open

Delete instance on instantiation failiure#1714
ivarne wants to merge 4 commits intomainfrom
bug/delete-instance-on-instantiation-failure

Conversation

@ivarne
Copy link
Copy Markdown
Member

@ivarne ivarne commented Mar 26, 2026

When instantiation fails, we previously left a half initialised instance that likely misses the data element with appLogic and fails to open in our frontend.

This attempts to delete the instance if an exception happens in the init code, but we still have the issue of application crashes that might cause deletion to fail or the full app container to crash.

Summary by CodeRabbit

  • Bug Fixes
    • Automatically attempts to delete partially created instances when creation, prefill/storage, or copy operations fail, reducing orphaned data.
    • Cleanup attempts are best-effort: deletion failures are caught and logged while original errors are returned.
    • Added guards to only attempt cleanup when an instance identifier is available, preventing secondary errors.

@ivarne ivarne requested a review from martinothamar March 26, 2026 07:48
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 26, 2026

📝 Walkthrough

Walkthrough

Exception handling in InstancesController was updated to perform best-effort hard deletion of partially created instances on various failure paths (Post, PostSimplified, CopyInstance). Deletion attempts are guarded, failures are logged, and original errors are still returned. A new private helper TryDeleteInstance(...) centralizes deletion logic.

Changes

Cohort / File(s) Summary
InstanceController: deletion and error handling
src/Altinn.App.Api/Controllers/InstancesController.cs
Added private TryDeleteInstance(Instance? targetInstance) helper. Post/Post exception paths now call the helper instead of inlining _instanceClient.DeleteInstance(...). PostSimplified makes instance nullable (Instance?) and only attempts deletion when instance?.Id exists; deletion exceptions are caught and logged separately. CopyInstance flow is wrapped in a try/catch that best-effort deletes a partially created targetInstance via the helper before returning an exception response. Deletion failures are logged and do not replace the original error response.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Delete instance on instantiation failiure' contains a typo ('failiure' instead of 'failure') but accurately describes the main change: adding deletion logic when instantiation fails.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bug/delete-instance-on-instantiation-failure

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread src/Altinn.App.Api/Controllers/InstancesController.cs Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/Altinn.App.Api/Controllers/InstancesController.cs (1)

880-896: Consider adding similar cleanup logic to CopyInstance for consistency.

The CopyInstance method creates an instance (line 880) and then performs additional operations that could fail (lines 888-896). If these fail, the instance will be left in an orphaned state, similar to the issue being fixed in Post and PostSimplified.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Altinn.App.Api/Controllers/InstancesController.cs` around lines 880 -
896, After creating the target instance in CopyInstance (the call to
_instanceClient.CreateInstance), wrap the subsequent operations
(CopyDataFromSourceInstance, _instanceClient.GetInstance, and
_processEngine.HandleEventsAndUpdateStorage) in a try/catch and on any exception
delete the newly created instance to avoid orphaned instances; call the
instance-delete API (e.g., _instanceClient.DeleteInstance or the controller's
existing instance removal helper) with the created targetInstance and
appropriate authentication/CancellationToken before rethrowing the exception so
cleanup mirrors the logic used in Post/PostSimplified.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Altinn.App.Api/Controllers/InstancesController.cs`:
- Around line 427-433: When attempting cleanup in InstancesController, wrap the
call to _instanceClient.DeleteInstance(...) (using
instance.InstanceOwner.PartyId and instance.Id) in its own try/catch so any
exception from DeleteInstance is logged but does not replace the original
instantiation exception; i.e., inside the catch that handles the failed
instantiation, call DeleteInstance inside try { await
_instanceClient.DeleteInstance(...); } catch (Exception delEx) {
_logger.LogWarning(delEx, "Cleanup DeleteInstance failed for instance
{InstanceId}"); } and then rethrow or propagate the original instantiation
exception (or use ExceptionDispatchInfo.Capture(originalEx).Throw() if
rethrowing) so the original error is preserved.
- Around line 733-744: The deletion call to _instanceClient.DeleteInstance
inside InstancesController should be wrapped in a try-catch so deletion failures
are logged and do not mask the original exception; update the code around the
existing if (instance?.Id is not null) block to catch Exception (e.g., catch
(Exception ex)) when calling _instanceClient.DeleteInstance, log the error with
contextual info (include instance.InstanceOwner.PartyId and instance.Id or the
parsed GUID) via the controller's logger (e.g., _logger.LogError) and do not
rethrow so the original exception remains the primary failure.

---

Nitpick comments:
In `@src/Altinn.App.Api/Controllers/InstancesController.cs`:
- Around line 880-896: After creating the target instance in CopyInstance (the
call to _instanceClient.CreateInstance), wrap the subsequent operations
(CopyDataFromSourceInstance, _instanceClient.GetInstance, and
_processEngine.HandleEventsAndUpdateStorage) in a try/catch and on any exception
delete the newly created instance to avoid orphaned instances; call the
instance-delete API (e.g., _instanceClient.DeleteInstance or the controller's
existing instance removal helper) with the created targetInstance and
appropriate authentication/CancellationToken before rethrowing the exception so
cleanup mirrors the logic used in Post/PostSimplified.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3452a484-ae92-4420-a100-71179f1025aa

📥 Commits

Reviewing files that changed from the base of the PR and between 099cf7a and 3702f15.

📒 Files selected for processing (1)
  • src/Altinn.App.Api/Controllers/InstancesController.cs

Comment thread src/Altinn.App.Api/Controllers/InstancesController.cs Outdated
@ivarne ivarne added backport This PR should be cherry-picked onto older release branches bugfix Label Pull requests with bugfix. Used when generation releasenotes labels Mar 26, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Altinn.App.Api/Controllers/InstancesController.cs (1)

744-770: ⚠️ Potential issue | 🔴 Critical

Missing closing brace for the if block causes compilation failure.

The if (instance?.Id is not null) block opened on line 744 is never closed. Line 765 closes the inner catch block, but the outer if block remains unclosed before the return statement. This is the cause of the pipeline failure.

🐛 Proposed fix to add missing closing brace
                 catch (Exception deleteException)
                 {
                     _logger.LogError(
                         deleteException,
                         "Failed to delete instance {InstanceId} after unsuccessful instantiation. Manual cleanup might be required.",
                         instance.Id
                     );
+                }
             }
             return ExceptionResponse(
                 exception,
                 $"Instantiation of appId {org}/{app} failed for party {instanceTemplate.InstanceOwner?.PartyId}"
             );
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Altinn.App.Api/Controllers/InstancesController.cs` around lines 744 -
770, Add the missing closing brace to properly close the if (instance?.Id is not
null) block that wraps the try/catch which calls
_instanceClient.DeleteInstance(...); specifically insert a '}' after the catch
block (just before the existing return ExceptionResponse(...) line) so the
try/catch is enclosed by the if and compilation succeeds in the
InstancesController class.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/Altinn.App.Api/Controllers/InstancesController.cs`:
- Around line 744-770: Add the missing closing brace to properly close the if
(instance?.Id is not null) block that wraps the try/catch which calls
_instanceClient.DeleteInstance(...); specifically insert a '}' after the catch
block (just before the existing return ExceptionResponse(...) line) so the
try/catch is enclosed by the if and compilation succeeds in the
InstancesController class.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ed446319-b570-4da7-aea0-757b11a18a74

📥 Commits

Reviewing files that changed from the base of the PR and between 3702f15 and 725ca80.

📒 Files selected for processing (1)
  • src/Altinn.App.Api/Controllers/InstancesController.cs

@ivarne ivarne enabled auto-merge (squash) March 26, 2026 12:08
Comment thread src/Altinn.App.Api/Controllers/InstancesController.cs Fixed
Comment thread src/Altinn.App.Api/Controllers/InstancesController.cs Dismissed
Comment thread src/Altinn.App.Api/Controllers/InstancesController.cs Fixed
Comment thread src/Altinn.App.Api/Controllers/InstancesController.cs Fixed
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Altinn.App.Api/Controllers/InstancesController.cs`:
- Around line 427-444: The catch path currently calls
_instanceClient.DeleteInstance directly (and may attempt deletion twice) and
returns ExceptionResponse instead of the original prefillProblem; refactor so
both the normal path and the catch path call a single best-effort cleanup helper
(e.g., TryCleanupInstanceAsync(instance) that wraps
_instanceClient.DeleteInstance and logs errors) and ensure the handler returns
the original prefillProblem (not ExceptionResponse) after invoking the helper;
update references to _instanceClient.DeleteInstance, deleteException, and
prefillProblem accordingly so deletion is centralized and not retried twice.
- Around line 744-766: The CopyInstance flow still lacks the best-effort orphan
cleanup: wrap the create/copy/process sequence that calls
CopyDataFromSourceInstance(...) and HandleEventsAndUpdateStorage(...) in a
try/catch and on any exception attempt the same delete logic used elsewhere
(parse party id from instance.InstanceOwner.PartyId and instance id GUID from
instance.Id.Split("/")[1], call _instanceClient.DeleteInstance(..., hard: true,
authenticationMethod: null, CancellationToken.None)), checking instance?.Id is
not null before deleting; if the delete throws, log it with _logger.LogError
including the delete exception and the instance.Id for diagnostics.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0690ff1a-fcf5-41d5-b9de-61e3010b1245

📥 Commits

Reviewing files that changed from the base of the PR and between 725ca80 and 0c8623d.

📒 Files selected for processing (1)
  • src/Altinn.App.Api/Controllers/InstancesController.cs

Comment thread src/Altinn.App.Api/Controllers/InstancesController.cs Outdated
Comment thread src/Altinn.App.Api/Controllers/InstancesController.cs
@ivarne ivarne disabled auto-merge March 26, 2026 12:25
Comment thread src/Altinn.App.Api/Controllers/InstancesController.cs Dismissed
Comment on lines +735 to +742
catch (Exception deleteException)
{
_logger.LogError(
deleteException,
"Failed to delete instance {InstanceId} after unsuccessful instantiation. Manual cleanup might be required.",
instance.Id
);
}

Check notice

Code scanning / CodeQL

Generic catch clause Note

Generic catch clause.

Copilot Autofix

AI 22 days ago

In general, the fix is to replace the generic catch (Exception) used for the delete‑cleanup with one or more specific exception types that represent expected, non‑fatal failures from _instanceClient.DeleteInstance, and optionally add a final catch that only rethrows or does minimal handling. Given the surrounding code already uses PlatformHttpException when talking to the instance client, the most appropriate targeted catch type here is PlatformHttpException. That exception indicates a failure in the underlying platform HTTP call (e.g., instance not found, forbidden, server error) and is precisely what we want to log and ignore in best‑effort cleanup.

Concretely, within src/Altinn.App.Api/Controllers/InstancesController.cs, in the inner try/catch responsible for deleting the instance after a failed instantiation, change catch (Exception deleteException) to catch (PlatformHttpException deleteException). This does not alter control flow: we still try to delete, and on failure we log the error and continue to return the original exception via ExceptionResponse. No new methods or imports are required because PlatformHttpException is already in use earlier in the same method and its namespace is already imported.

Suggested changeset 1
src/Altinn.App.Api/Controllers/InstancesController.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/Altinn.App.Api/Controllers/InstancesController.cs b/src/Altinn.App.Api/Controllers/InstancesController.cs
--- a/src/Altinn.App.Api/Controllers/InstancesController.cs
+++ b/src/Altinn.App.Api/Controllers/InstancesController.cs
@@ -732,7 +732,7 @@
                         CancellationToken.None
                     );
                 }
-                catch (Exception deleteException)
+                catch (PlatformHttpException deleteException)
                 {
                     _logger.LogError(
                         deleteException,
EOF
@@ -732,7 +732,7 @@
CancellationToken.None
);
}
catch (Exception deleteException)
catch (PlatformHttpException deleteException)
{
_logger.LogError(
deleteException,
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +909 to +917
catch (Exception exception)
{
await TryDeleteInstance(targetInstance);

string url = SelfLinkHelper.BuildFrontendSelfLink(targetInstance, Request);
return ExceptionResponse(
exception,
$"Copying instance {instanceOwnerPartyId}/{instanceGuid} failed for party {targetInstance?.InstanceOwner?.PartyId}"
);
}

Check notice

Code scanning / CodeQL

Generic catch clause Note

Generic catch clause.

Copilot Autofix

AI 22 days ago

General approach: narrow the generic catch so that clearly distinct categories of exceptions are either rethrown or handled separately, and only “expected” operational failures are handled by the current ExceptionResponse logic. For controller actions that support cancellation tokens, it is especially important not to swallow OperationCanceledException (and related exceptions), to avoid blocking cooperative cancellation and to let ASP.NET Core handle aborted requests appropriately.

Concrete best fix here without changing existing functionality for normal failures:

  1. Add a specific catch (OperationCanceledException) block before the generic catch. In this block, rethrow using throw; so that framework-level cancellation handling remains intact. If you prefer explicit behavior you could translate it into a 499/Client Closed Request or similar, but that would change externally visible behavior; simply rethrow is safer.
  2. Optionally consider other special cases (like TaskCanceledException if used directly), but OperationCanceledException is usually sufficient because most task cancellation surfaces as this type.
  3. Keep the existing catch (Exception exception) block for all other exceptions, including the cleanup (TryDeleteInstance) and the call to ExceptionResponse, to avoid changing the error contract.

All changes are within src/Altinn.App.Api/Controllers/InstancesController.cs in the shown method, around lines 879–917. No new imports are required because OperationCanceledException is in System, which is implicitly available.

Suggested changeset 1
src/Altinn.App.Api/Controllers/InstancesController.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/Altinn.App.Api/Controllers/InstancesController.cs b/src/Altinn.App.Api/Controllers/InstancesController.cs
--- a/src/Altinn.App.Api/Controllers/InstancesController.cs
+++ b/src/Altinn.App.Api/Controllers/InstancesController.cs
@@ -906,6 +906,11 @@
 
             return Redirect(url);
         }
+        catch (OperationCanceledException)
+        {
+            // Preserve cancellation semantics by letting the exception propagate
+            throw;
+        }
         catch (Exception exception)
         {
             await TryDeleteInstance(targetInstance);
EOF
@@ -906,6 +906,11 @@

return Redirect(url);
}
catch (OperationCanceledException)
{
// Preserve cancellation semantics by letting the exception propagate
throw;
}
catch (Exception exception)
{
await TryDeleteInstance(targetInstance);
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +934 to +941
catch (Exception deleteException)
{
_logger.LogError(
deleteException,
"Failed to delete instance {InstanceId} after unsuccessful copy. Manual cleanup might be required.",
targetInstance.Id
);
}

Check notice

Code scanning / CodeQL

Generic catch clause Note

Generic catch clause.

Copilot Autofix

AI 22 days ago

In general, the issue is fixed by catching only the exception types you reasonably expect and can handle, and treating unexpected exceptions differently (e.g., letting them bubble up, or at least logging them as unexpected). Here, we likely need to keep the method from throwing (because it's a “try” cleanup method), but we can still narrow the main catch and then have a final catch (Exception) only as a last resort.

Best fix without changing existing functionality: introduce specific catches for common storage-related exceptions (such as FormatException if the IDs are malformed, ArgumentException for parameter issues, and potentially OperationCanceledException if cancellation could occur), log them as before, and then keep a final generic catch for anything else. To slightly reduce breadth while maintaining behavior, we can add a generic catch that logs with a more explicit “unexpected error” message but still swallows it. This ensures we haven't changed outward behavior (no new exceptions escape), but we make it clearer what we consider expected vs. unexpected issues and satisfy the intent of the CodeQL rule by avoiding a single broad catch.

Concretely, in src/Altinn.App.Api/Controllers/InstancesController.cs, around lines 920–941, we will:

  • Add a more specific catch for FormatException (in case PartyId or Id parsing fails).
  • Add a more specific catch for ArgumentException (defensive for bad arguments to DeleteInstance).
  • Optionally add a specific catch for OperationCanceledException if the client can throw it.
  • Keep a final catch (Exception deleteException) as a safety net, but now it’s only for truly unexpected errors and can log accordingly.

No new using directives are required, since FormatException, ArgumentException, and OperationCanceledException are in System, which is already implicitly available.

Suggested changeset 1
src/Altinn.App.Api/Controllers/InstancesController.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/Altinn.App.Api/Controllers/InstancesController.cs b/src/Altinn.App.Api/Controllers/InstancesController.cs
--- a/src/Altinn.App.Api/Controllers/InstancesController.cs
+++ b/src/Altinn.App.Api/Controllers/InstancesController.cs
@@ -931,11 +931,35 @@
                     CancellationToken.None
                 );
             }
+            catch (FormatException formatException)
+            {
+                _logger.LogError(
+                    formatException,
+                    "Failed to delete instance {InstanceId} after unsuccessful copy due to invalid identifier format. Manual cleanup might be required.",
+                    targetInstance.Id
+                );
+            }
+            catch (ArgumentException argumentException)
+            {
+                _logger.LogError(
+                    argumentException,
+                    "Failed to delete instance {InstanceId} after unsuccessful copy due to invalid argument. Manual cleanup might be required.",
+                    targetInstance.Id
+                );
+            }
+            catch (OperationCanceledException canceledException)
+            {
+                _logger.LogError(
+                    canceledException,
+                    "Deletion of instance {InstanceId} after unsuccessful copy was canceled. Manual cleanup might be required.",
+                    targetInstance.Id
+                );
+            }
             catch (Exception deleteException)
             {
                 _logger.LogError(
                     deleteException,
-                    "Failed to delete instance {InstanceId} after unsuccessful copy. Manual cleanup might be required.",
+                    "Unexpected error when deleting instance {InstanceId} after unsuccessful copy. Manual cleanup might be required.",
                     targetInstance.Id
                 );
             }
EOF
@@ -931,11 +931,35 @@
CancellationToken.None
);
}
catch (FormatException formatException)
{
_logger.LogError(
formatException,
"Failed to delete instance {InstanceId} after unsuccessful copy due to invalid identifier format. Manual cleanup might be required.",
targetInstance.Id
);
}
catch (ArgumentException argumentException)
{
_logger.LogError(
argumentException,
"Failed to delete instance {InstanceId} after unsuccessful copy due to invalid argument. Manual cleanup might be required.",
targetInstance.Id
);
}
catch (OperationCanceledException canceledException)
{
_logger.LogError(
canceledException,
"Deletion of instance {InstanceId} after unsuccessful copy was canceled. Manual cleanup might be required.",
targetInstance.Id
);
}
catch (Exception deleteException)
{
_logger.LogError(
deleteException,
"Failed to delete instance {InstanceId} after unsuccessful copy. Manual cleanup might be required.",
"Unexpected error when deleting instance {InstanceId} after unsuccessful copy. Manual cleanup might be required.",
targetInstance.Id
);
}
Copilot is powered by AI and may make mistakes. Always verify output.
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
37.0% Coverage on New Code (required ≥ 65%)
28.6% Condition Coverage on New Code (required ≥ 65%)

See analysis details on SonarQube Cloud

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Altinn.App.Api/Controllers/InstancesController.cs`:
- Around line 879-917: The try/catch currently surrounds post-create response
work so TryDeleteInstance(targetInstance) may hard-delete a copy after
successful HandleEventsAndUpdateStorage; restrict rollback to the creation/copy
initialization steps by enclosing only the sequence that can require rollback
(calls to _instanceClient.CreateInstance, CopyDataFromSourceInstance,
_instanceClient.GetInstance and _processEngine.HandleEventsAndUpdateStorage) in
the try, let RegisterEvent, SelfLinkHelper.BuildFrontendSelfLink and Redirect
happen after the try completes, and keep the catch calling
TryDeleteInstance(targetInstance) and returning ExceptionResponse(exception,
...) so that deletion only occurs for failures during initialization, not for
failures building/returning the response.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: beb2b2db-5a67-4fb7-a600-fe0d3f8e63ce

📥 Commits

Reviewing files that changed from the base of the PR and between 0c8623d and d5f432b.

📒 Files selected for processing (1)
  • src/Altinn.App.Api/Controllers/InstancesController.cs

Comment on lines +879 to +917
try
{
targetInstance = await _instanceClient.CreateInstance(
org,
app,
targetInstance,
authenticationMethod: null,
CancellationToken.None
);

await CopyDataFromSourceInstance(application, targetInstance, sourceInstance);
await CopyDataFromSourceInstance(application, targetInstance, sourceInstance);

targetInstance = await _instanceClient.GetInstance(
targetInstance,
authenticationMethod: null,
CancellationToken.None
);
targetInstance = await _instanceClient.GetInstance(
targetInstance,
authenticationMethod: null,
CancellationToken.None
);

await _processEngine.HandleEventsAndUpdateStorage(
targetInstance,
null,
startResult.ProcessStateChange?.Events
);

await RegisterEvent("app.instance.created", targetInstance);

await _processEngine.HandleEventsAndUpdateStorage(targetInstance, null, startResult.ProcessStateChange?.Events);
string url = SelfLinkHelper.BuildFrontendSelfLink(targetInstance, Request);

await RegisterEvent("app.instance.created", targetInstance);
return Redirect(url);
}
catch (Exception exception)
{
await TryDeleteInstance(targetInstance);

string url = SelfLinkHelper.BuildFrontendSelfLink(targetInstance, Request);
return ExceptionResponse(
exception,
$"Copying instance {instanceOwnerPartyId}/{instanceGuid} failed for party {targetInstance?.InstanceOwner?.PartyId}"
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Keep rollback scoped to copy initialization.

The catch also covers Lines 903-907, so any later exception in response construction will call TryDeleteInstance(targetInstance) and hard-delete a copy that already passed HandleEventsAndUpdateStorage(...). Move the post-create response work outside the rollback scope.

♻️ Proposed fix
         try
         {
             targetInstance = await _instanceClient.CreateInstance(
                 org,
                 app,
@@
             await _processEngine.HandleEventsAndUpdateStorage(
                 targetInstance,
                 null,
                 startResult.ProcessStateChange?.Events
             );
-
-            await RegisterEvent("app.instance.created", targetInstance);
-
-            string url = SelfLinkHelper.BuildFrontendSelfLink(targetInstance, Request);
-
-            return Redirect(url);
         }
         catch (Exception exception)
         {
             await TryDeleteInstance(targetInstance);

             return ExceptionResponse(
                 exception,
                 $"Copying instance {instanceOwnerPartyId}/{instanceGuid} failed for party {targetInstance?.InstanceOwner?.PartyId}"
             );
         }
+
+        await RegisterEvent("app.instance.created", targetInstance);
+        string url = SelfLinkHelper.BuildFrontendSelfLink(targetInstance, Request);
+        return Redirect(url);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Altinn.App.Api/Controllers/InstancesController.cs` around lines 879 -
917, The try/catch currently surrounds post-create response work so
TryDeleteInstance(targetInstance) may hard-delete a copy after successful
HandleEventsAndUpdateStorage; restrict rollback to the creation/copy
initialization steps by enclosing only the sequence that can require rollback
(calls to _instanceClient.CreateInstance, CopyDataFromSourceInstance,
_instanceClient.GetInstance and _processEngine.HandleEventsAndUpdateStorage) in
the try, let RegisterEvent, SelfLinkHelper.BuildFrontendSelfLink and Redirect
happen after the try completes, and keep the catch calling
TryDeleteInstance(targetInstance) and returning ExceptionResponse(exception,
...) so that deletion only occurs for failures during initialization, not for
failures building/returning the response.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport This PR should be cherry-picked onto older release branches bugfix Label Pull requests with bugfix. Used when generation releasenotes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants