Problem
WorkspaceService.create() and fork() can return Err after they have already registered the new workspace row, and the row stays in config. The caller (UI or API) is told that creation failed, but a workspace exists: it appears in the sidebar and in task_list. When the throw comes after the default-consent grant (create()'s immediate path, or fork()), that row also carries unrelated-messaging consent, so other task trees can discover and message a workspace the user was told does not exist.
Examples, verified at main 9b1f7f9:
create(): !completeMetadata returns Err("Failed to retrieve workspace metadata") right after registration. A throw from secretsToRecord(...) or syncCodeWorkspaceFiles(...) reaches the outer catch. Neither path rolls back; only the sanitize-error and registration-write paths call abortUnsanitizedCreation.
fork(): a throw from workspaceGoalService.inheritFromFork or syncCodeWorkspaceFiles reaches the outer catch after addWorkspace succeeded.
#4814 (for #4455) only makes these exits clear the pending default-consent mark (fail closed). It deliberately does not change row retention.
Proposed direction
Either roll the registration back on these exits (as abortUnsanitizedCreation / abortForkRegistration already do for their own failures), or return success with a degraded status when the workspace is usable. If the row is kept, revoke any consent granted in the same call. Decide per exit: a throw after background init has started probably means the workspace is real and the Err is the bug.
Refs #4455, #4814, #4440
Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high • Cost: $12.63
Problem
WorkspaceService.create()andfork()can returnErrafter they have already registered the new workspace row, and the row stays in config. The caller (UI or API) is told that creation failed, but a workspace exists: it appears in the sidebar and intask_list. When the throw comes after the default-consent grant (create()'s immediate path, orfork()), that row also carries unrelated-messaging consent, so other task trees can discover and message a workspace the user was told does not exist.Examples, verified at main 9b1f7f9:
create():!completeMetadatareturnsErr("Failed to retrieve workspace metadata")right after registration. A throw fromsecretsToRecord(...)orsyncCodeWorkspaceFiles(...)reaches the outercatch. Neither path rolls back; only the sanitize-error and registration-write paths callabortUnsanitizedCreation.fork(): a throw fromworkspaceGoalService.inheritFromForkorsyncCodeWorkspaceFilesreaches the outercatchafteraddWorkspacesucceeded.#4814 (for #4455) only makes these exits clear the pending default-consent mark (fail closed). It deliberately does not change row retention.
Proposed direction
Either roll the registration back on these exits (as
abortUnsanitizedCreation/abortForkRegistrationalready do for their own failures), or return success with a degraded status when the workspace is usable. If the row is kept, revoke any consent granted in the same call. Decide per exit: a throw after background init has started probably means the workspace is real and the Err is the bug.Refs #4455, #4814, #4440
Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:high• Cost:$12.63