Skip to content

feat(worktree): add optional custom branch and worktree names#1380

Open
drochag wants to merge 2 commits intogeneralaction:mainfrom
drochag:feat/custom-branch-worktree-names
Open

feat(worktree): add optional custom branch and worktree names#1380
drochag wants to merge 2 commits intogeneralaction:mainfrom
drochag:feat/custom-branch-worktree-names

Conversation

@drochag
Copy link

@drochag drochag commented Mar 10, 2026

Summary

  • Allow users to specify custom branch names and worktree directory names when creating tasks
  • When left empty, falls back to existing auto-generation behavior ({prefix}/{slugged-name}-{hash})
  • Real-time validation for branch names (Git ref rules) and worktree names (filesystem rules)
  • Dynamic placeholder shows suggested worktree folder name derived from branch name

Changes

  • New file: src/renderer/lib/nameValidation.ts - validation helpers
  • Backend: Updated WorktreeService, WorktreePoolService, and worktreeIpc to accept custom names
  • Frontend: Added input fields in TaskAdvancedSettings with validation and dynamic placeholder
  • Types: Updated electron-api.d.ts with new optional parameters

Test plan

  • Create task with empty custom fields → uses auto-generated names
  • Create task with custom branch name only → uses custom branch, auto-generates worktree folder
  • Create task with both custom names → uses both custom values
  • Enter invalid branch name (with spaces) → shows validation error
  • Enter invalid worktree name (with /) → shows validation error
  • Type branch name → worktree folder placeholder updates dynamically
Screenshot 2026-03-09 at 5 34 43 p m

Allow users to specify custom branch names and worktree directory names
when creating tasks. When left empty, falls back to the existing
auto-generation behavior.

- Add validation helpers for branch/worktree names (nameValidation.ts)
- Update WorktreeService and WorktreePoolService to accept custom names
- Add input fields in TaskAdvancedSettings with real-time validation
- Show dynamic placeholder suggesting derived worktree name from branch
- Update IPC handlers and types throughout the stack
@vercel
Copy link

vercel bot commented Mar 10, 2026

@drochag is attempting to deploy a commit to the General Action Team on Vercel.

A member of the Team first needs to authorize it.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 10, 2026

Greptile Summary

This PR adds optional custom branch and worktree directory names when creating tasks, with real-time frontend validation and backend sanitization. The overall structure is well-designed, but there are two logic bugs and one UX inconsistency worth addressing:

Key changes:

  • New src/renderer/lib/nameValidation.ts provides client-side validation for branch names (git ref rules) and worktree names (filesystem rules)
  • TaskAdvancedSettings gains two controlled inputs with a dynamic placeholder derived from the entered branch name
  • WorktreeService, WorktreePoolService, and worktreeIpc thread custom names through to the name-generation step, with fallback to auto-generation when empty

Issues identified:

  1. [Logic] In worktree:create IPC handler, custom names are silently dropped when the project is remote — remoteGitService.createWorktree is called without them, so users see their custom names accepted in the UI but the worktree uses auto-generated names instead.

  2. [Logic] sanitizeWorktreeName can return an empty string for inputs like --- (all dashes), and neither WorktreeService nor WorktreePoolService has a fallback. This results in an invalid worktree path pointing to the worktrees/ directory itself, causing git-worktree operations to fail.

  3. [UX] validateBranchName misses several characters that git forbids (~, ^, :, ?, *, [, \), so users see no error in the frontend but the backend silently transforms these characters to -, creating a confusing mismatch.

  4. [Maintenance] sanitizeBranchName and sanitizeWorktreeName are duplicated verbatim in both WorktreeService and WorktreePoolService, risking divergence and requiring fixes in two places.

Confidence Score: 3/5

  • Two logic bugs risk silent data loss (remote custom names dropped) and potential filesystem errors (empty sanitized name); the UX and local-project paths are well-implemented.
  • The PR is mostly safe for local-project workflows, but two logic issues could cause real failures in production. Custom names silently drop for remote projects (user sees names accepted, but worktree uses auto-generated names instead), and sanitizeWorktreeName can produce an empty string with no fallback, pointing the worktree path to the worktrees/ directory itself. The empty-name case in particular could cause git to fail with a cryptic error or leave the parent directory in an invalid state. Frontend UX and local plumbing are well-structured and consistent end-to-end.
  • src/main/services/worktreeIpc.ts (remote path drops custom names), src/main/services/WorktreeService.ts and src/main/services/WorktreePoolService.ts (empty sanitized name with no fallback).

Sequence Diagram

sequenceDiagram
    actor User
    participant TaskModal
    participant nameValidation
    participant taskCreationService
    participant worktreeIpc (main)
    participant WorktreePoolService
    participant WorktreeService

    User->>TaskModal: Type customBranchName / customWorktreeName
    TaskModal->>nameValidation: validateBranchName(val)
    nameValidation-->>TaskModal: null or error string
    TaskModal->>nameValidation: validateWorktreeName(val)
    nameValidation-->>TaskModal: null or error string

    User->>TaskModal: Submit form
    TaskModal->>taskCreationService: createTask({ customBranchName, customWorktreeName, ... })

    alt Pool reserve available (local project)
        taskCreationService->>worktreeIpc (main): worktreeClaimReserveAndSaveTask(args)
        worktreeIpc (main)->>WorktreePoolService: claimReserve(..., customBranchName, customWorktreeName)
        WorktreePoolService->>WorktreePoolService: transformReserve → sanitize names
        WorktreePoolService-->>worktreeIpc (main): ClaimResult
        worktreeIpc (main)-->>taskCreationService: { success, worktree }
    else No pool reserve / fallback (local project)
        taskCreationService->>worktreeIpc (main): worktreeCreate(args)
        worktreeIpc (main)->>WorktreeService: createWorktree(..., customBranchName, customWorktreeName)
        WorktreeService->>WorktreeService: sanitize names → git worktree add
        WorktreeService-->>worktreeIpc (main): WorktreeInfo
        worktreeIpc (main)-->>taskCreationService: { success, worktree }
    else Remote project (bug: custom names dropped)
        taskCreationService->>worktreeIpc (main): worktreeCreate(args)
        worktreeIpc (main)->>worktreeIpc (main): isRemoteProject = true
        worktreeIpc (main)->>worktreeIpc (main): remoteGitService.createWorktree(taskName, baseRef) ⚠️ custom names lost
        worktreeIpc (main)-->>taskCreationService: { success, worktree }
    end

    taskCreationService-->>TaskModal: task created
Loading

Comments Outside Diff (1)

  1. src/main/services/worktreeIpc.ts, line 69-90 (link)

    Custom branch and worktree names are silently dropped for remote projects. When isRemoteProject(project) is true, the handler receives customBranchName and customWorktreeName from the caller (lines 59–60) but never forwards them to remoteGitService.createWorktree(...) (lines 75–80).

    // Current call — custom names are lost:
    const remote = await remoteGitService.createWorktree(
      project.sshConnectionId,
      project.remotePath,
      args.taskName,
      baseRef
    );

    The user sees their custom names accepted in the UI, but the created worktree uses auto-generated names instead. If remoteGitService.createWorktree does not yet support custom names, either:

    1. Add support for custom names to the remote service, or
    2. Log a warning so users know the limitation exists.

Last reviewed commit: 3cbb407

Comment on lines +414 to +422
/** Sanitize worktree directory name to ensure it's a valid path component */
private sanitizeWorktreeName(name: string): string {
return name
.replace(/[/\\:*?"<>|]/g, '-')
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '')
.slice(0, 100);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sanitizeWorktreeName can return an empty string when the input consists entirely of characters that get converted to dashes. For example, an input like --- or ??? passes frontend validation (those characters aren't caught by validateWorktreeName) but produces an empty string here, because:

  1. Characters like ? are replaced with -
  2. Multiple dashes are collapsed to one via /-+/g
  3. Leading/trailing dashes are stripped via /^-|-$/g
  4. Result: empty string

This causes the worktree path to resolve to the worktrees/ directory itself rather than a subdirectory, leading to invalid git-worktree operations or filesystem errors.

A fallback is needed when sanitization results in an empty string:

private sanitizeWorktreeName(name: string): string {
  const sanitized = name
    .replace(/[/\\:*?"<>|]/g, '-')
    .replace(/\s+/g, '-')
    .replace(/-+/g, '-')
    .replace(/^-|-$/g, '')
    .slice(0, 100);
  return sanitized || `worktree-${this.generateShortHash()}`;
}

This issue also exists in WorktreePoolService.sanitizeWorktreeName (line 697).

Comment on lines +18 to +53
export function validateBranchName(name: string): string | null {
// Empty is valid (will auto-generate)
if (!name || !name.trim()) {
return null;
}

const trimmed = name.trim();

if (trimmed.length > 250) {
return 'Branch name must be 250 characters or less';
}

if (/\s/.test(trimmed)) {
return 'Branch name cannot contain whitespace';
}

if (trimmed.includes('..')) {
return 'Branch name cannot contain ".."';
}

if (trimmed.startsWith('-')) {
return 'Branch name cannot start with "-"';
}

if (trimmed.endsWith('.lock')) {
return 'Branch name cannot end with ".lock"';
}

if (trimmed.includes('@{')) {
return 'Branch name cannot contain "@{"';
}

// Git also disallows some other characters, but we'll let the backend handle those
// via sanitization. These are the most common user errors.

return null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validateBranchName misses several characters that git check-ref-format forbids: ~, ^, :, ?, *, [, and \. These characters are currently accepted by the frontend validator but silently transformed to - by the backend sanitizeBranchName, creating a confusing UX gap.

For example, a user typing feature~1 sees no validation error in the UI but ends up with a different branch name (feature-1) than what they entered.

Adding these characters to the validation would make the frontend and backend consistent:

if (/[\s~^:?*[\\\x00-\x1f\x7f]/.test(trimmed)) {
  return 'Branch name contains invalid characters (spaces, ~, ^, :, ?, *, [, \\)';
}

Comment on lines +682 to +704
/** Sanitize branch name to ensure it's a valid Git ref */
private sanitizeBranchName(name: string): string {
let n = name
.replace(/\s+/g, '-')
.replace(/[^A-Za-z0-9._\/-]+/g, '-')
.replace(/-+/g, '-')
.replace(/\/+/g, '/');
n = n.replace(/^[./-]+/, '').replace(/[./-]+$/, '');
if (!n || n === 'HEAD') {
n = `emdash/${this.slugify('task')}-${this.generateShortHash()}`;
}
return n;
}

/** Sanitize worktree directory name to ensure it's a valid path component */
private sanitizeWorktreeName(name: string): string {
return name
.replace(/[/\\:*?"<>|]/g, '-')
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '')
.slice(0, 100);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sanitizeBranchName and sanitizeWorktreeName are duplicated verbatim in both WorktreeService (lines 401–422) and WorktreePoolService (lines 682–704). Keeping two copies means any future bug-fix or rule change must be applied in both places — and divergence is one commit away from happening.

Consider extracting these helpers into a shared module (e.g. src/main/lib/worktreeNameUtils.ts) and importing them in both services to prevent drift.

- Extract sanitization functions to shared module (worktreeNameUtils.ts)
- Add fallback for empty sanitized worktree names
- Add missing invalid characters to frontend branch name validation
  (~, ^, :, ?, *, [, \, and control characters)
@arnestrickmann
Copy link
Contributor

Thanks for opening the PR and for your interest in contributing!

Will have a look and let you know shortly:)

@kenan-xin
Copy link

Have been waiting for this for awhile, I had to rename branch name manually all the while

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants