wavetermdev/waveterm · error

draft app '%s' already exists

Error message

draft app '%s' already exists

What it means

Renames are also blocked if the new name already exists in the draft namespace. When os.Stat(newDraftDir) succeeds, RenameLocalApp returns "draft app '%s' already exists" so a local rename cannot silently collide with an in-progress draft of the same name.

Source

Thrown at pkg/waveappstore/waveappstore.go:694

	if _, err := os.Stat(oldDraftDir); err == nil {
		draftExists = true
	} else if !os.IsNotExist(err) {
		return fmt.Errorf("failed to check draft app: %w", err)
	}

	if !localExists && !draftExists {
		return fmt.Errorf("app '%s' does not exist in local or draft namespace", appName)
	}

	// Check if new app name already exists in either namespace
	if _, err := os.Stat(newLocalDir); err == nil {
		return fmt.Errorf("local app '%s' already exists", newAppName)
	} else if !os.IsNotExist(err) {
		return fmt.Errorf("failed to check if new local app exists: %w", err)
	}

	if _, err := os.Stat(newDraftDir); err == nil {
		return fmt.Errorf("draft app '%s' already exists", newAppName)
	} else if !os.IsNotExist(err) {
		return fmt.Errorf("failed to check if new draft app exists: %w", err)
	}

	// Rename local app if it exists
	if localExists {
		if err := os.Rename(oldLocalDir, newLocalDir); err != nil {
			return fmt.Errorf("failed to rename local app: %w", err)
		}
	}

	// Rename draft app if it exists
	if draftExists {
		if err := os.Rename(oldDraftDir, newDraftDir); err != nil {
			// If local was renamed but draft fails, try to rollback local rename
			if localExists {
				if rollbackErr := os.Rename(newLocalDir, oldLocalDir); rollbackErr != nil {
					return fmt.Errorf("failed to rename draft app (and failed to rollback local rename: %v): %w", rollbackErr, err)

View on GitHub (pinned to a4447c1563)

Solutions

  1. Choose a newName not present in either the local or draft namespace
  2. Delete the stale draft directory (~/waveapps/draft/<newName>) if it is no longer needed
  3. Publish or clean up the existing draft first, then retry the rename

Example fix

// before
RenameLocalApp("myapp", "wipapp") // draft:wipapp exists
// after
os.RemoveAll(filepath.Join(home, "waveapps", "draft", "wipapp")) // only if stale
RenameLocalApp("myapp", "wipapp")
Defensive patterns

Strategy: validation

Validate before calling

func draftNameTaken(name string) bool {
    _, err := os.Stat(filepath.Join(wavebase.GetHomeDir(), "waveapps", "draft", name))
    return err == nil
}
// require !draftNameTaken(newName) before renaming

Try / catch

if err := waveappstore.RenameLocalApp(oldName, newName); err != nil {
    if strings.Contains(err.Error(), "draft app") && strings.Contains(err.Error(), "already exists") {
        // prompt user to resolve/remove the draft or pick another name
        return fmt.Errorf("a draft named %q exists; resolve it first", newName)
    }
    return err
}

Prevention

When it happens

Trigger: Calling RenameLocalApp(oldName, newName) where ~/waveapps/draft/<newName> exists — e.g. a draft with that name was created earlier or a previous draft/publish cycle left the directory behind.

Common situations: Renaming to a name that was previously drafted but never published, leftover draft directories after interrupted workflows, or intentional drafts the user forgot about.

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/94d22d637d619314. Report an issue: GitHub.