{"record":{"id":"8de8d7072a4dd281","repo":"gitbutlerapp/gitbutler","slug":"project-already-exists","errorCode":null,"errorMessage":"project already exists","messagePattern":"project already exists","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/gitbutler-project/src/project.rs","lineNumber":318,"sourceCode":"}\n\nimpl AddProjectOutcome {\n    /// This is for tests only.\n    ///\n    /// Unwraps the `Project` if the project was actually added.\n    /// Panics if it was not.\n    pub fn unwrap_project(self) -> Project {\n        match self {\n            AddProjectOutcome::Added(p) => p,\n            _ => panic!(\"called `AddProjectOutcome::unwrap_project()` on a non-project outcome\"),\n        }\n    }\n\n    /// Try to get the `Project`, returning an error if it was not added.\n    pub fn try_project(self) -> anyhow::Result<Project> {\n        match self {\n            AddProjectOutcome::Added(p) => Ok(p),\n            AddProjectOutcome::AlreadyExists(_) => Err(anyhow::anyhow!(\"project already exists\")),\n            AddProjectOutcome::PathNotFound => Err(anyhow::anyhow!(\"project path not found\")),\n            AddProjectOutcome::NotADirectory => {\n                Err(anyhow::anyhow!(\"project path is not a directory\"))\n            }\n            AddProjectOutcome::BareRepository => {\n                Err(anyhow::anyhow!(\"bare repositories are not supported\"))\n            }\n            AddProjectOutcome::NonMainWorktree => {\n                Err(anyhow::anyhow!(\"non-main worktrees are not supported\"))\n            }\n            AddProjectOutcome::NoWorkdir => Err(anyhow::anyhow!(\"no workdir found for repository\")),\n            AddProjectOutcome::NoDotGitDirectory => {\n                Err(anyhow::anyhow!(\"no .git directory found in repository\"))\n            }\n            AddProjectOutcome::ReftableRefFormatUnsupported => Err(anyhow::anyhow!(\n                \"unsupported repository reference format: reftable\"\n            )),\n            AddProjectOutcome::NotAGitRepository(msg) => {","sourceCodeStart":300,"sourceCodeEnd":336,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/gitbutler-project/src/project.rs#L300-L336","documentation":"AddProjectOutcome::try_project maps the AlreadyExists variant to this error. add_project deduplicates by path: if a Project for that path is already registered, no second one is created and the outcome carries the existing Project. Calling try_project() on that outcome discards the existing project and turns an expected, benign situation into an Err.","triggerScenarios":"Calling project::add(path) twice with the same path; re-adding a repository after re-install without removing the old record; two components racing to add the same path (first wins); path normalization differences that still resolve to the same stored project.","commonSituations":"Re-running app onboarding over an existing setup; sync/automation re-adding known projects; UI double-submit of the add-project form.","solutions":["Match on AddProjectOutcome instead of calling try_project: AlreadyExists(Project) hands you the existing project to reuse","Before adding, list registered projects and check whether the path is already present","Make add idempotent in UI/automation: treat AlreadyExists as success","If the stale record is wrong (e.g. moved path), delete the project first, then add again"],"exampleFix":"// before\nlet project = outcome.try_project()?; // 'project already exists'\n\n// after: handle the outcome enum instead of collapsing it\nlet project = match outcome {\n    AddProjectOutcome::Added(p) => p,\n    AddProjectOutcome::AlreadyExists(existing) => existing, // reuse it\n    other => return Err(anyhow!(\"add_project failed: {other}\")),\n};","handlingStrategy":"type-guard","validationCode":"// check registered projects first if you need idempotence without enum handling\nlet already = controller.list_all_projects()?.iter().any(|p| p.worktree_dir == path);\nlet outcome = if already { /* reuse existing flow */ } else { add_project(&path, ...) };","typeGuard":"// the outcome enum itself is the guard — match instead of collapsing via try_project\nfn added_or_existing(outcome: AddProjectOutcome) -> anyhow::Result<Project> {\n    match outcome {\n        AddProjectOutcome::Added(p) | AddProjectOutcome::AlreadyExists(p) => Ok(p),\n        other => Err(anyhow!(\"add_project failed: {other}\")),\n    }\n}","tryCatchPattern":"match add_project(&path, &controller, ...) {\n    AddProjectOutcome::Added(p) => Ok(p),\n    AddProjectOutcome::AlreadyExists(existing) => {\n        tracing::info!(\"project already registered — reusing it\");\n        Ok(existing)\n    }\n    other => Err(anyhow!(\"{other}\")),\n}","preventionTips":["Always match on AddProjectOutcome instead of calling try_project when duplicates are expected","Make add flows idempotent in the UI (double-submit safe)","When the stored project is stale (moved path), delete it before re-adding instead of relying on AlreadyExists"],"tags":["gitbutler","project","duplicate","validation"],"backgroundTag":"duplicate-project-path","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-23T16:17:53.355Z"}