claude-code - 💡(How to fix) Fix Launch claude from anywhere: interactive recent-projects picker

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Code Example

# Claude Code Project PickerPowerShell
# ----------------------------------------
# Wraps the `claude` command. When invoked with no arguments from your home
# directory, shows an interactive menu of recent Claude projects sorted by last
# activity. Navigate with arrow keys; Enter to open, R to resume the last
# session, Esc to start a new session in the current directory.
#
# INSTALL: Add to your PowerShell profile ($PROFILE):
#   . "$HOME\Documents\claude-project-picker\claude-project-picker.ps1"


function claude {
    $realClaude = (Get-Command claude -CommandType Application -ErrorAction SilentlyContinue |
                   Select-Object -First 1).Source

    if (-not $realClaude) {
        Write-Host "claude: command not found" -ForegroundColor Red
        return
    }

    if ($args.Count -gt 0 -or $PWD.Path -ne $env:USERPROFILE) {
        & $realClaude @args
        return
    }

    $projectsDir = Join-Path $env:USERPROFILE ".claude\projects"
    if (-not (Test-Path $projectsDir)) {
        & $realClaude
        return
    }

    $projects = Get-ChildItem $projectsDir -Directory -ErrorAction SilentlyContinue |
        ForEach-Object {
            $jsonls = Get-ChildItem $_.FullName -Filter "*.jsonl" -ErrorAction SilentlyContinue
            $latest = $jsonls | Sort-Object LastWriteTime -Descending | Select-Object -First 1
            if (-not $latest) { return }
            $line = Get-Content $latest.FullName |
                        Where-Object { $_ -match '"cwd"' } |
                        Select-Object -First 1
            if ($line -match '"cwd":"((?:[^"\\]|\\.)*)"') {
                $cwd = $matches[1] -replace '\\\\', '\'
                if (Test-Path $cwd) {
                    [PSCustomObject]@{
                        Path      = $cwd
                        LastWrite = $latest.LastWriteTime
                        Chats     = $jsonls.Count
                    }
                }
            }
        } |
        Sort-Object LastWrite -Descending |
        Group-Object Path |
        ForEach-Object { $_.Group[0] }

    if (-not $projects -or $projects.Count -eq 0) {
        Write-Host "No Claude projects found. Starting here." -ForegroundColor DarkCyan
        & $realClaude
        return
    }

    $termWidth = [Console]::WindowWidth - 1
    $maxName   = ($projects |
                  ForEach-Object { (Split-Path $_.Path -Leaf).Length } |
                  Measure-Object -Maximum).Maximum

    $formatLine = {
        param($p, $isSelected)
        $prefix = if ($isSelected) { "  > " } else { "    " }
        $name   = (Split-Path $p.Path -Leaf).PadRight($maxName)
        $dt     = $p.LastWrite.ToString("yyyy-MM-dd HH:mm")
        $chats  = if ($p.Chats -eq 1) { "1 chat" } else { "$($p.Chats) chats" }
        $full   = "$prefix$name   $dt   $chats   ($($p.Path))"
        if ($full.Length -gt $termWidth) { $full.Substring(0, $termWidth - 3) + "..." }
        else                             { $full.PadRight($termWidth) }
    }

    Write-Host "  Enter open  |  R resume  |  Esc start here`n" -ForegroundColor DarkGray

    $selected = 0
    $menuTop  = [Console]::CursorTop

    $drawMenu = {
        [Console]::SetCursorPosition(0, $menuTop)
        for ($i = 0; $i -lt $projects.Count; $i++) {
            $line = & $formatLine $projects[$i] ($i -eq $selected)
            if ($i -eq $selected) { Write-Host $line -ForegroundColor Cyan }
            else                   { Write-Host $line }
        }
    }

    & $drawMenu

    $done = $false
    while (-not $done) {
        $key = [Console]::ReadKey($true)
        switch ($key.Key) {
            'UpArrow'   { if ($selected -gt 0)                   { $selected--; & $drawMenu } }
            'DownArrow' { if ($selected -lt $projects.Count - 1) { $selected++; & $drawMenu } }
            'Enter'  {
                Write-Host ""
                Set-Location $projects[$selected].Path
                & $realClaude
                $done = $true
            }
            'R'      {
                Write-Host ""
                Set-Location $projects[$selected].Path
                & $realClaude --resume
                $done = $true
            }
            'Escape' {
                Write-Host ""
                & $realClaude
                $done = $true
            }
        }
    }
}
RAW_BUFFERClick to expand / collapse

Preflight Checklist

  • I have searched existing requests and this feature hasn't been requested yet
  • This is a single feature request (not multiple features)

Problem Statement

When I open a new terminal, I'm always in my home directory. Running claude with no arguments just starts a blank new session — there's no way to get back to a recent project without already knowing the path, cd-ing there manually, and then running claude again. If I have several active projects I switch between regularly, this friction adds up constantly.

Proposed Solution

When claude is run with no arguments from the home directory, show an interactive project picker listing recent projects from ~/.claude/projects/, sorted by last activity. Each row would show the project name, last active time, and number of chats. Arrow keys to navigate, Enter to open, R to resume the last session in that project, Esc to start fresh in the current directory.

Alternative Solutions

I built a PowerShell shell wrapper that does this today by reading the existing .jsonl session files — no changes to Claude Code needed on the data side. I also wrote a bash/zsh equivalent. Both are included below as a reference implementation.

# Claude Code Project Picker — PowerShell
# ----------------------------------------
# Wraps the `claude` command. When invoked with no arguments from your home
# directory, shows an interactive menu of recent Claude projects sorted by last
# activity. Navigate with arrow keys; Enter to open, R to resume the last
# session, Esc to start a new session in the current directory.
#
# INSTALL: Add to your PowerShell profile ($PROFILE):
#   . "$HOME\Documents\claude-project-picker\claude-project-picker.ps1"


function claude {
    $realClaude = (Get-Command claude -CommandType Application -ErrorAction SilentlyContinue |
                   Select-Object -First 1).Source

    if (-not $realClaude) {
        Write-Host "claude: command not found" -ForegroundColor Red
        return
    }

    if ($args.Count -gt 0 -or $PWD.Path -ne $env:USERPROFILE) {
        & $realClaude @args
        return
    }

    $projectsDir = Join-Path $env:USERPROFILE ".claude\projects"
    if (-not (Test-Path $projectsDir)) {
        & $realClaude
        return
    }

    $projects = Get-ChildItem $projectsDir -Directory -ErrorAction SilentlyContinue |
        ForEach-Object {
            $jsonls = Get-ChildItem $_.FullName -Filter "*.jsonl" -ErrorAction SilentlyContinue
            $latest = $jsonls | Sort-Object LastWriteTime -Descending | Select-Object -First 1
            if (-not $latest) { return }
            $line = Get-Content $latest.FullName |
                        Where-Object { $_ -match '"cwd"' } |
                        Select-Object -First 1
            if ($line -match '"cwd":"((?:[^"\\]|\\.)*)"') {
                $cwd = $matches[1] -replace '\\\\', '\'
                if (Test-Path $cwd) {
                    [PSCustomObject]@{
                        Path      = $cwd
                        LastWrite = $latest.LastWriteTime
                        Chats     = $jsonls.Count
                    }
                }
            }
        } |
        Sort-Object LastWrite -Descending |
        Group-Object Path |
        ForEach-Object { $_.Group[0] }

    if (-not $projects -or $projects.Count -eq 0) {
        Write-Host "No Claude projects found. Starting here." -ForegroundColor DarkCyan
        & $realClaude
        return
    }

    $termWidth = [Console]::WindowWidth - 1
    $maxName   = ($projects |
                  ForEach-Object { (Split-Path $_.Path -Leaf).Length } |
                  Measure-Object -Maximum).Maximum

    $formatLine = {
        param($p, $isSelected)
        $prefix = if ($isSelected) { "  > " } else { "    " }
        $name   = (Split-Path $p.Path -Leaf).PadRight($maxName)
        $dt     = $p.LastWrite.ToString("yyyy-MM-dd HH:mm")
        $chats  = if ($p.Chats -eq 1) { "1 chat" } else { "$($p.Chats) chats" }
        $full   = "$prefix$name   $dt   $chats   ($($p.Path))"
        if ($full.Length -gt $termWidth) { $full.Substring(0, $termWidth - 3) + "..." }
        else                             { $full.PadRight($termWidth) }
    }

    Write-Host "  Enter open  |  R resume  |  Esc start here`n" -ForegroundColor DarkGray

    $selected = 0
    $menuTop  = [Console]::CursorTop

    $drawMenu = {
        [Console]::SetCursorPosition(0, $menuTop)
        for ($i = 0; $i -lt $projects.Count; $i++) {
            $line = & $formatLine $projects[$i] ($i -eq $selected)
            if ($i -eq $selected) { Write-Host $line -ForegroundColor Cyan }
            else                   { Write-Host $line }
        }
    }

    & $drawMenu

    $done = $false
    while (-not $done) {
        $key = [Console]::ReadKey($true)
        switch ($key.Key) {
            'UpArrow'   { if ($selected -gt 0)                   { $selected--; & $drawMenu } }
            'DownArrow' { if ($selected -lt $projects.Count - 1) { $selected++; & $drawMenu } }
            'Enter'  {
                Write-Host ""
                Set-Location $projects[$selected].Path
                & $realClaude
                $done = $true
            }
            'R'      {
                Write-Host ""
                Set-Location $projects[$selected].Path
                & $realClaude --resume
                $done = $true
            }
            'Escape' {
                Write-Host ""
                & $realClaude
                $done = $true
            }
        }
    }
}

claude-project-picker.sh

Priority

High - Significant impact on productivity

Feature Category

CLI commands and flags

Use Case Example

  1. I open a new terminal — it lands in my home directory
  2. I type claude
  3. A list appears showing my five most recently active projects with timestamps
  4. I arrow down to the one I want and press Enter
  5. Claude Code opens in that project directory, ready to go

Additional Context

https://github.com/user-attachments/assets/df2a5658-9c4a-432d-b7f6-16d5735039a9

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix Launch claude from anywhere: interactive recent-projects picker