From caef3cf5e8981b031907a852680fb03b3272e53a Mon Sep 17 00:00:00 2001 From: Padreug Date: Tue, 16 Jun 2026 00:07:39 +0200 Subject: [PATCH] fix(accounts): 409 when admin-adding an account that already exists add_account no-ops if the Open directive is already present but returned a normal-looking dict, so the admin endpoint reported success ('created (sync pending)') for a duplicate. Return an already_existed flag and raise 409 from the endpoint. Also anchor the existence check on the Open directive with a trailing-boundary match so a prefix (Expenses:Gas) doesn't match a longer sibling (Expenses:GasStation). The flag is additive, so the idempotent user-account path keeps no-opping silently. Co-Authored-By: Claude Opus 4.8 (1M context) --- fava_client.py | 20 ++++++++++++++++---- views_api.py | 8 +++++++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/fava_client.py b/fava_client.py index afc964f..f176031 100644 --- a/fava_client.py +++ b/fava_client.py @@ -1643,10 +1643,22 @@ class FavaClient: sha256sum = source_data["sha256sum"] source = source_data["source"] - # Step 2: Check if account already exists (may have been created by concurrent request) - if f"open {account_name}" in source: + # Step 2: Check if account already exists (may have been + # created by a concurrent request). Anchor on the Open + # directive and require the account to be followed by + # whitespace/end-of-line so a prefix (Expenses:Gas) does + # not match a longer sibling (Expenses:GasStation). + if re.search( + rf"open {re.escape(account_name)}(?:\s|$)", + source, + re.MULTILINE, + ): logger.info(f"Account {account_name} already exists in {target_file}") - return {"data": sha256sum, "mtime": source_data.get("mtime", "")} + return { + "data": sha256sum, + "mtime": source_data.get("mtime", ""), + "already_existed": True, + } # Step 3: Always append at end of file. # Post-split layout, each include file has one mutation @@ -1700,7 +1712,7 @@ class FavaClient: result = response.json() logger.info(f"Added account {account_name} to {target_file} with currencies {currencies}") - return result + return {**result, "already_existed": False} except httpx.HTTPStatusError as e: # Check for checksum conflict (HTTP 412 Precondition Failed or similar) diff --git a/views_api.py b/views_api.py index 7dce7c3..d88b292 100644 --- a/views_api.py +++ b/views_api.py @@ -3695,13 +3695,19 @@ async def api_admin_add_chart_account( if payload.description: metadata["description"] = payload.description - await fava.add_account( + result = await fava.add_account( account_name=payload.name, currencies=payload.currencies, target_file="accounts/chart.beancount", metadata=metadata, ) + if result.get("already_existed"): + raise HTTPException( + status_code=HTTPStatus.CONFLICT, + detail=f"Account {payload.name} already exists", + ) + # Mirror into libra DB so permissions / metadata layer sees it. from .account_sync import sync_single_account_from_beancount synced = await sync_single_account_from_beancount(payload.name)