Skip to content

Clickable filenames in ls DIR/ output resolve against CWD, silently opening wrong files #9908

@rndjams

Description

@rndjams

Pre-submit Checks

Describe the bug

When a block's command lists the contents of a directory other than the block's working directory — for example ls -la some/dir/, ls subdir, ls /etc/ — clickable filename detection in the block's output uses the block's CWD as the resolution root instead of the directory that was listed.

This produces three user-visible symptoms:

  1. Silent misresolution (most harmful). If a file by the same name as one listed in ls DIR/ also exists in the block's CWD, clicking the filename in the output opens the wrong file (the one from CWD) without any indication. The most obvious tell is README.md: clicking README.md in ls subdir/ output opens the root README.md in whatever application is configured for Markdown files, not subdir/README.md.
  2. False negatives for files. If DIR/foo.txt exists but CWD/foo.txt does not, foo.txt is not clickable at all in the ls DIR/ output.
  3. False negatives for directories. Subdirectories listed by ls DIR/ are not clickable. (In contrast, directories listed by ls with no argument are clickable and open in Finder via SystemGeneric routing.)

Symptoms 2 and 3 are related to (but distinct from) the enhancement request in #4636. This report focuses on the silent-misresolution root cause that #4636 does not surface.

Related but distinct prior art: #4636 is an enhancement request for clickability of files outside CWD; this bug is about silent misresolution when a same-named file exists in CWD. #3439 is a feature request for right-click menus on ls output.

To reproduce

Symptom 1 — silent misresolution (the most important case):

# Set up
mkdir -p /tmp/warp-repro/subdir
echo "root readme"      > /tmp/warp-repro/README.md
echo "subdir readme"    > /tmp/warp-repro/subdir/README.md
cd /tmp/warp-repro

# Run in Warp
ls -la subdir/
# Output includes:
#   -rw-r--r-- … README.md

# Click README.md in the ls output.
  • Actual: Warp opens /tmp/warp-repro/README.md ("root readme") in the Markdown viewer.
  • Expected: Warp opens /tmp/warp-repro/subdir/README.md ("subdir readme").

Symptom 2 — false negative for unique files:

cd /tmp/warp-repro
ls subdir/          # output includes 'README.md'
rm README.md        # now only subdir/README.md exists
ls subdir/          # clicking README.md in the new block: nothing happens

Symptom 3 — directories in ls DIR/ output not clickable:

ls -la /tmp/warp-repro/        # subdir is clickable → opens Finder
ls -la /                       # /tmp is clickable → opens Finder
ls -la subdir/                 # any subdirectory row: NOT clickable

Scrollback reproduction (to show the bug is stable across block history):

ls -la subdir/     # block A (CWD = /tmp/warp-repro)
cd /tmp            # move away
# scroll back to block A, click README.md
  • Actual: Same wrong README.md opens (from /tmp/warp-repro, not from subdir/).

This is consistent with how per-block resolution works today: each block stores its own pwd, so scrollback is internally correct — the bug is that the stored pwd is the wrong resolution root when the command listed a different directory.

Expected behavior

When a block's command is a directory-listing command with a directory argument, clickable path detection in that block's output must use that directory (joined to the block's stored pwd) as the primary resolution root.

Specifically:

  1. Given block command ls DIR/ (or ls -la DIR/, ls --color=always DIR/, etc.) with block.pwd = CWD, bare filename NAME in the output must resolve to CWD/DIR/NAME if that path exists.
  2. If both CWD/NAME and CWD/DIR/NAME exist, the block's listed-argument wins. (There is no scenario where a user running ls DIR/ wants clicks on its output to open files outside DIR.)
  3. If only CWD/DIR/NAME exists, NAME must be clickable.
  4. Directory entries in ls DIR/ output must be clickable and open in Finder, matching the existing behavior for ls with no argument.
  5. The behavior must hold for scrollback blocks, including blocks restored from a previous session, because each block carries its own pwd and command.
  6. The fix must not affect blocks whose output is already rooted (e.g. find DIR/ -name '*.md' output contains DIR/sub/foo.md, which already resolves correctly against CWD). Avoid double-joining.

Initial command coverage

The fix should start with ls (the most common case). A user-configurable list of listing-aware commands accommodates exa, eza, lsd, and similar modern replacements, and allows users to add their own. tree is a natural extension but is blocked by a separate tokenizer bug (box-drawing characters not recognized as link separators — tracked in a sibling issue).

Out of scope for this issue (explicitly defer to follow-ups):

  • ls -R DIR/ — recursive listings have multiple root contexts marked by DIR/SUB: headers; needs per-section resolution.
  • ls DIR1/ DIR2/ — multi-argument listings; needs per-section resolution.
  • Piped/redirected output (ls DIR/ > file; cat file) — argument context is lost at the pipe boundary.

Root cause (from source reading)

Pointers to the code paths so maintainers can sanity-check the framing:

  • app/src/terminal/view/link_detection.rs, scan_for_file_path: for BlockList clicks, the working directory passed to the resolver is block.pwd().map(String::from) — the CWD at the time the command ran. The block's command is not consulted.
  • app/src/util/file.rs, absolute_path_if_valid: joins the candidate to the working_directory passed in, or treats it as absolute. No awareness of any directory argument on the block's command.
  • app/src/terminal/model/grid/grid_handler.rs:1097, possible_file_paths_at_point: tokenizes the clicked row into candidate substrings. The tokenizer works correctly for bare filenames like README.md; the bug is strictly in the resolution step, not tokenization (for tree-style output, tokenization is also broken — see sibling issue).
  • app/src/terminal/model/block/serialized_block.rs: blocks serialize pwd and stylized_command, so the fix works durably across scrollback and session restore.
  • app/src/terminal/view/open_in_warp.rs, check_openable_in_warp: already uses warp_completer::parsers to extract positional arguments from commands in FILE_VIEWER_COMMANDS. The primary fix can reuse this parsing path for the new listing-command set.

Proposed fix — implementation summary

I've prototyped a fix on a local branch (rndjams/fix-ls-dir-link-resolution). Summary:

What's in the proposed PR:

  • A new pure-logic helper in crates/warp_util/src/listing_command.rs:

    pub const DEFAULT_LISTING_COMMANDS: &[&str] = &["ls", "exa", "eza", "lsd"];
    
    pub fn listing_command_argument_dir(
        command: &str,
        pwd: &Path,
        listing_commands: &[&str],
    ) -> Option<PathBuf>;

    Uses shlex::split for shell tokenization (handles '...', "...", backslash escapes). Skips leading KEY=VALUE env-var prefixes (LS_COLORS=auto ls DIR), skips flags (-*), returns the first path-like positional joined to pwd, gated on is_dir().

  • 22 unit tests covering realistic ls variants: bare path, trailing slash, absolute path, tilde expansion, quoted args with spaces, multi-arg (first wins), flag+value combos like --color=always DIR, env-var prefixes, malformed input, custom command sets, negative tests for find/cat/git, plus 5 alias-specific tests exercising the resolved-name override path.

  • Wire-up in app/src/terminal/view/link_detection.rs:

    • scan_for_file_path reads the block's command + pwd and computes listing_dir_to_scan via the helper.
    • The helper also accepts an alias-resolved command name via Block::top_level_command(sessions), so user aliases like alias ll='ls -l' trigger the fix without needing ll in DEFAULT_LISTING_COMMANDS.
    • compute_valid_paths tries listing_dir resolution first via a new try_resolve closure; if that returns nothing, falls through to the existing ShellNative(working_directory) path. Behavior is strictly additive — non-listing commands and blocks without a directory argument are unaffected.

Deliberate scope boundaries (V1):

  • Aliases with baked-in positional args are a known limitation. Single-level alias names are resolved via Block::top_level_command(sessions) so alias ll='ls -l'll DIR/ correctly triggers the fix. But if an alias bakes in a positional (alias lsd='ls /tmp'lsd DIR/), the helper picks the user-typed DIR/ as the first positional, not the aliased /tmp. Rare in practice; documented in the helper's doc-comment.
  • No user-facing setting in V1. DEFAULT_LISTING_COMMANDS is a const slice in warp_util. Making it a TOML setting is a separate, orthogonal concern and felt out of scope for a bug fix. Happy to add it in the same PR if maintainers prefer.
  • Recursive listings (ls -R DIR/, tree DIR) are out of scope. Each section header (SUB:) changes the resolution root; needs per-section tracking.
  • Multi-arg listings (ls DIR1/ DIR2/) are out of scope. First arg wins today.
  • Piped/redirected output (ls DIR/ > file; cat file) is a permanent limitation. The argument context is lost at the pipe boundary.
  • tree output tokenization is blocked on a separate tokenizer bug — box-drawing characters (, , , ) aren't in FILE_LINK_SEPARATORS. Tracked in a sibling issue so tree benefits immediately once both fixes land.

Validation (local):

  • cargo test -p warp_util --lib listing_command — all 22 tests pass (17 original + 5 alias-specific).
  • cargo test -p warp --lib util:: — 98 existing tests pass. 1 unrelated pre-existing flake in util::git::tests::detached_tag_display_returns_short_sha (environment-sensitive, unrelated to this change).
  • cargo clippy -p warp --all-targets -- -D warnings — clean.
  • cargo fmt --all --check — clean.

Happy to open the PR once this issue is triaged.

Screenshots, videos, and logs

The repro steps above are minimal and deterministic; happy to record a short screen capture on request.

Operating system (OS)

macOS

Operating system and version

macOS 26.4.1 (Darwin 25.4.0, arm64)

Shell Version

zsh 5.9

Current Warp version

v0.2026.04.27.15.32.stable_03

Regression

No, this bug or issue has existed throughout my experience using Warp

Recent working Warp date

No response

Additional context

Sibling issue: tokenizer does not treat box-drawing characters (, , , ) as link separators, preventing tree and eza --tree leaf filenames from being tokenized as clickable candidates. That issue is complementary to this one — even after listing-aware resolution lands, tree output will still be unclickable until the tokenizer is fixed.

Does this block you from using Warp daily?

No

Is this an issue only in Warp?

Yes, I confirmed that this only happens in Warp, not other terminals.

Warp Internal (ignore): linear-label:b9d78064-c89e-4973-b153-5178a31ee54e

None

Metadata

Metadata

Assignees

Labels

area:shell-terminalTerminal input/output, shell integration, prompt behavior, and block rendering.bugSomething isn't working.os:macmacOS-specific behavior, regressions, or requests.ready-to-specThe issue is ready for a product and technical spec.repro:highThe report includes enough evidence that the issue appears highly reproducible.triagedIssue has received an initial automated triage pass.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions