Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code 1181c35105 chore: improve monitoring for Non-idempotent ActivateWorkspace mutation fails on
Added `!isSubmitting` guard to the `handleKeyDown` Enter key handler in `CreateWorkspace.tsx`. The button already had `disabled={!isValid || isSubmitting}`, but the Enter key handler bypassed this check, allowing duplicate mutation submissions when pressing Enter rapidly. This reduces noise from duplicate submissions reaching the server.
2026-03-06 20:46:58 +00:00
Sonarly Claude Code 56c5a69694 Non-idempotent ActivateWorkspace mutation fails on retry after successful activation
https://sonarly.com/issue/4069?type=bug

The `activateWorkspace` mutation throws "Workspace is not pending creation" when called a second time because the workspace was already successfully activated by a prior request. The operation is not idempotent, and multiple retry paths exist in the frontend.

Fix: Made `activateWorkspace` idempotent for the `ACTIVE` state. When the workspace is already active (from a prior successful activation), the method now returns the existing workspace entity instead of throwing an error. This handles all retry scenarios: Apollo RetryLink auto-retries, user retries after post-activation frontend failures, and double-submissions.

The check is placed **before** the existing `ONGOING_CREATION` and `PENDING_CREATION` checks, so:
- `ACTIVE` → returns workspace (new idempotent behavior)
- `ONGOING_CREATION` → still throws "Workspace is already being created"
- `INACTIVE` / `SUSPENDED` → still throws "Workspace is not pending creation"

A `logger.log` call is added to track when the idempotent path is taken, providing observability without generating Sentry errors.
2026-03-06 20:46:58 +00:00
2 changed files with 13 additions and 1 deletions
@@ -144,7 +144,7 @@ export const CreateWorkspace = () => {
);
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === Key.Enter) {
if (event.key === Key.Enter && !isSubmitting) {
event.preventDefault();
handleSubmit(onSubmit)();
}
@@ -304,6 +304,18 @@ export class WorkspaceService extends TypeOrmQueryService<WorkspaceEntity> {
throw new BadRequestException("'displayName' not provided");
}
if (
workspace.activationStatus === WorkspaceActivationStatus.ACTIVE
) {
this.logger.log(
`Workspace ${workspace.id} is already active, returning existing workspace`,
);
return await this.workspaceRepository.findOneBy({
id: workspace.id,
});
}
if (
workspace.activationStatus === WorkspaceActivationStatus.ONGOING_CREATION
) {