Skip to content

Open terminal path:line links in the preferred editor - #7162

Closed
Eridanus117 wants to merge 9 commits into
manaflow-ai:mainfrom
Eridanus117:cmux-fileline-editor
Closed

Eridanus117 wants to merge 9 commits into
manaflow-ai:mainfrom
Eridanus117:cmux-fileline-editor

Conversation

@Eridanus117

@Eridanus117 Eridanus117 commented Jul 2, 2026 •

Copy link
Copy Markdown
Contributor

What

Terminal file links of the form path:line[:column] — the convention Claude Code and Codex print for click-to-open — now open at the referenced line in the user's preferred editor.

Before this change, only bare paths clicked through; the :line suffix broke three ways:

Input Before After
/Users/me/app/main.swift:42 silent no-op (URL(fileURLWithPath:) keeps :42, file doesn't exist) opens main.swift at line 42
src/App.swift:42:5 (relative) misrouted to the browser omnibar (https://src/App.swift…) opens App.swift at line 42, col 5
/Users/me/README.md (no line) unchanged (in-app viewer / external) unchanged

How

The bug and fix are entirely in the Swift open/route layer (Ghostty already frames the full path:line[:col] as one link token). Four small pieces on the existing package seams:

  • String.splitTerminalPathLineSuffix() (CmuxTerminalCore) — peels a trailing :line / :line:column (positive integers only) off a token.
  • TerminalPathResolver.resolveOpenURLFileReference(_:cwd:) — resolves a token to an existing file plus optional line/column. Each candidate spelling is probed literal-first (so a file that really ends in :42 still wins) then line-stripped; http/https text is never treated as a file, and the file-existence probe gates everything else.
  • TerminalFileReference.fileURL — carries line/column across the package boundary as a #L<line>[:<column>] URL fragment (single encode site).
  • PreferredEditorService.open — decodes that fragment and hands path:line:column (plus -g) to VS Code-family editors (code, code-insiders, cursor, cursor-insiders, windsurf), respecting an existing -g/--goto in the configured command. Other editors and the system-open fallback get the plain file path.

The terminal open-URL handler routes any resolved line reference to the editor (in-app viewers can't jump to a line); this is also what fixes the relative-path→browser misroute.

Scope

Covers the terminal link click path (GHOSTTY_ACTION_OPEN_URL), which is where all three reported failures occur. The editor-dispatch path is now the single fragment-aware primitive, so the cmd-click "word under cursor" fallback (bare filenames Ghostty doesn't underline, #2199 territory) can supply a line reference in a one-line follow-up without touching this logic again.

Testing

Package-level swift test (Xcode 26.5), red→green two-commit:

  • swift test --package-path Packages/macOS/CmuxTerminalCore — 187 tests green (split, reference resolution incl. the host:port-with-same-named-dir exclusion, fragment encoding).
  • swift test --package-path Packages/macOS/CmuxWorkspaces — 106 tests green (editor -g / path:line:column dispatch — incl. quoted app-bundle binary detection and existing---goto handling — end-to-end through open).

The first commit adds the tests against stubs / current behavior and is red; the second implements and is green. No new user-facing strings (localization audit: n/a).

The four resolution/dispatch primitives are covered above; the ~15-line GHOSTTY_ACTION_OPEN_URL wiring that calls them is a thin routing shim (line ref → editor; no-line → existing cmux/external routing) and was reviewed by inspection — I couldn't build the full app locally (the Ghostty CLI helper needs a zig toolchain I don't have installed), so the app-level build and the three repro tokens (/abs/readme.md:1, relative dir/App.swift:1, foo.swift:42:5) should be confirmed on CI / a maintainer machine.

Credit

Supersedes #3977 by @austinywang, which delivered the same user-facing behavior. That PR predates #5894, which extracted the terminal resolver into CmuxTerminalCore / CmuxWorkspaces; rebasing it onto the new package seams amounts to a rewrite, so this is a focused reimplementation (~280 lines vs 886) adapting its :line split and editor -g dispatch. Full credit to @austinywang via Co-authored-by.


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Clicking terminal links like path:line[:column] now opens the file at that location in your preferred editor. This fixes broken absolute links and stops relative links from being sent to the browser.

  • New Features

    • Parse and resolve path:line[:column] in CmuxTerminalCore, carrying line/column as a #L… fragment.
    • Route any line-referenced file to the preferred editor; no-line paths follow existing cmux/external routing.
    • CmuxWorkspaces converts #L… to path:line[:column] for VS Code–family CLIs (code, code-insiders, cursor, cursor-insiders, windsurf) and adds -g unless present; other editors get the plain path.
  • Bug Fixes

    • Absolute paths like /app/main.swift:42 open at line 42; relative src/App.swift:42:5 opens in the editor, not the browser.
    • Hardened parsing and dispatch: require ASCII digits; fail closed on malformed fragments (only L<line>[:<column>] is accepted — reject #42, L42:, L42:abc, L42:0); ignore http/https and host:port; only treat line-stripped tokens that look like file paths.
    • Match /bin/sh -c tokenizing and detect VS Code–family CLIs anywhere in the command; do not treat "\--goto" as --goto, and add -g when needed.

Written for commit c05260e. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added support for resolving terminal/open-URL text with path:line / path:line:column, targeting the specified location in the preferred editor.
    • Preferred-editor launching now reads #L<line>[:<column>] fragments (and encodes them in produced file URLs) to enable goto-capable commands.
  • Bug Fixes
    • Prevented web URLs, word:number, and host:port-like strings from being misread as file references; trimmed/empty inputs don’t resolve.
    • Ensured fragment-malformed/invalid values fail closed and fallback opens omit fragments.
    • Updated in-app routing/open behavior to use the resolved file URL/path.
  • Tests
    • Added coverage for suffix parsing, resolution rules, fragment encoding, and editor invocation/goto handling.

Eridanus117 and others added 2 commits July 2, 2026 05:52
Terminal links of the form `path:line[:column]` (the convention Claude Code
and Codex print for click-to-open) do not open at the referenced line today:
absolute refs silently fail, relative refs are misrouted to the browser
omnibar. Introduce the resolution/opening primitives as stubs and cover the
target behavior with tests so this commit is red and the fix (next commit)
turns it green:

- `String.splitTerminalPathLineSuffix()` splits a trailing `:line[:column]`.
- `TerminalPathResolver.resolveOpenURLFileReference(_:cwd:)` resolves a token
  to an existing file plus optional line/column, probing the literal path
  first and the line-stripped path second.
- `TerminalFileReference.fileURL` carries line/column as a `#L…` fragment.
- `PreferredEditorService.open` should honor that fragment and hand a
  `path:line:column` argument (plus `-g`) to VS Code-family editors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolve terminal file links of the form `path:line[:column]` — the
convention Claude Code and Codex print for click-to-open — to the
referenced file and open it at that line in the configured editor:

- `String.splitTerminalPathLineSuffix()` peels a trailing `:line[:column]`.
- `TerminalPathResolver.resolveOpenURLFileReference(_:cwd:)` resolves a token
  to an existing file plus optional line/column, probing the literal path
  first (so a file that really ends in a colon-number wins) and the
  line-stripped path second; web URLs are never treated as file paths.
- `TerminalFileReference.fileURL` carries line/column across the package
  boundary as a `#L<line>[:<column>]` fragment.
- `PreferredEditorService.open` decodes that fragment and hands
  `path:line:column` (plus `-g`) to VS Code-family editors, falling back to
  the fragment-free file URL for other editors and the system opener.
- The terminal open-URL handler routes any line reference to the editor,
  which also fixes relative `dir/file.swift:line` links that previously fell
  through to the browser omnibar.

Verified with package-level swift test on CmuxTerminalCore and CmuxWorkspaces
(red without the fix, green with it). No new user-facing strings.

Supersedes manaflow-ai#3977 by @austinywang, which implemented the same user-facing
behavior before manaflow-ai#5894 extracted the resolver into CmuxTerminalCore /
CmuxWorkspaces; the `:line` split and editor `-g` dispatch are adapted from
that PR onto the new package seams.

Co-authored-by: austinpower1258 <austinwang115@gmail.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 2, 2026

Copy link
Copy Markdown

@Eridanus117 is attempting to deploy a commit to the Manaflow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a1cdfd48-709a-4896-8aa3-225dd7b4e380

📥 Commits

Reviewing files that changed from the base of the PR and between f17c2cc and c05260e.

📒 Files selected for processing (2)
  • Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/FileOpen/PreferredEditorService.swift
  • Packages/macOS/CmuxWorkspaces/Tests/CmuxWorkspacesTests/FileOpen/PreferredEditorLineReferenceTests.swift

📝 Walkthrough

Walkthrough

Adds terminal open-URL support for path:line[:column] references. The resolver parses and validates line suffixes, encodes line fragments into file URLs, opens them through the preferred editor with goto arguments, and updates terminal URL routing to use the new file-reference resolver.

Changes

Line/column reference resolution and editor opening

Layer / File(s) Summary
Path suffix parsing helper
Packages/macOS/CmuxTerminalCore/Sources/.../String+TerminalPathTokens.swift, Packages/macOS/CmuxTerminalCore/Tests/.../TerminalPathLineReferenceTests.swift
Adds splitTerminalPathLineSuffix() plus ASCII-only digit validation, with tests for valid and invalid line/column suffixes.
TerminalPathResolver open-URL file reference resolution
Packages/macOS/CmuxTerminalCore/Sources/.../TerminalPathResolver.swift, Packages/macOS/CmuxTerminalCore/Tests/.../TerminalPathLineReferenceTests.swift
Adds resolveOpenURLFileReference, file-path heuristics, standardized existence probing, and TerminalFileReference.fileURL fragment encoding, with resolver and URL-format tests.
Preferred editor goto invocation
Packages/macOS/CmuxWorkspaces/Sources/.../PreferredEditorService.swift, Packages/macOS/CmuxWorkspaces/Tests/.../PreferredEditorLineReferenceTests.swift
Updates editor launching to support #L<line>[:<column>] goto behavior, fragment-free fallback opens, and command token parsing, with new invocation tests.
Terminal open_url wiring
Sources/GhosttyTerminalView.swift
Switches open-url handling to the new file-reference resolver and routes line references to the preferred editor.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

  • manaflow-ai/cmux#7122: Also changes Sources/GhosttyTerminalView.swift open-url routing for terminal file links.
🚥 Pre-merge checks | ✅ 25
✅ Passed checks (25 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the main change: opening terminal path:line links in the preferred editor.
Description check ✅ Passed The description is detailed and covers the summary and testing, but it omits the demo video and review-trigger sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed PASS: The new file-open/editor path is either pure Sendable or explicitly @MainActor; no new implicit MainActor models, mutable Sendable refs, or background UI-store access.
Cmux Swift Blocking Runtime ✅ Passed The diff adds no new blocking primitives in shipped Swift; only test-only Task.sleep polling was added, while production changes are pure resolution/launch wiring.
Cmux Browser Automation Off-Main ✅ Passed PR only changes terminal path/open-url handling and preferred-editor dispatch; it does not touch browser.* socket routing, worker/mainActor policy, or policy tests.
Cmux Expensive Synchronous Load ✅ Passed PR only adds terminal path parsing, file-existence checks, and editor launch routing; no agent-history loader or large sync JSON/transcript load was added.
Cmux Cache Substitution Correctness ✅ Passed Diff only updates ghostty font/build code; no persistence/history/undo/snapshot path swaps a fresh read for cached/opportunistic data.
Cmux No Hacky Sleeps ✅ Passed PR changes are Swift-only; the no-hacky-sleeps rule covers non-Swift runtime scripts, so it doesn't apply.
Cmux Algorithmic Complexity ✅ Passed The new scans are over tiny fixed-size token/candidate lists, not user-scale collections; no nested rescans or hot-path sort/filter of scalable records were introduced.
Cmux Swift Concurrency ✅ Passed No new background queues/Combine/completion-handler APIs; the only added Task is a main-actor hop from Process.terminationHandler, an allowed OS callback boundary.
Cmux Swift @Concurrent ✅ Passed No changed nonisolated async or @concurrent misuse found; added file work is synchronous or explicitly hops back to MainActor.
Cmux Swift File And Package Boundaries ✅ Passed PASS: New production Swift files are small (223–319 lines); the huge Ghostty file only gets a ~20-line glue change, while parsing/resolution/editor logic lives in SwiftPM packages.
Cmux Swiftpm Lockfiles ✅ Passed The PR only bumps the ghostty submodule; no cmux-owned Package.swift, .gitignore, workflow, Xcode package-reference, or Package.resolved changes appear in the diff.
Cmux Swift Logging ✅ Passed No new print/debugPrint/dump/NSLog/Logger usage in the changed production Swift code; only existing DEBUG NSLog and a class-local Logger remain.
Cmux User-Facing Error Privacy ✅ Passed PASS: The changes only add path-resolution/editor-routing logic and tests; no new user-facing errors, alerts, or recovery copy expose vendor/internal details.
Cmux Full Internationalization ✅ Passed PR adds path/line parsing and routing only; no new user-facing Swift copy, catalogs, Info.plist, or web locale files were introduced.
Cmux Swiftui State Layout ✅ Passed No new SwiftUI state/layout patterns were introduced; the host-file change is AppKit routing in GhosttyNSView, and the other touched Swift files are non-UI helpers.
Cmux Architecture Rethink ✅ Passed Small correctness fix with clear owners: parsing, resolver, and editor dispatch are localized; no new polling, sleeps, observers, or split UI ownership were introduced.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed Diff only bumps ghostty submodule font/build Zig files; no Swift window code or cmuxAuxiliaryWindowIdentifiers changes.
Cmux Source Artifacts ✅ Passed Changed paths are source, tests, docs, config, and a deliberate submodule SHA pin; no logs/build output/temp/cache/scratch artifacts are introduced.
Cmux No Test Or Debug Seam In Production Source ✅ Passed No added DEBUG/test-only members or seam names in production Sources; new APIs are runtime-facing and have production callers.
Cmux No Ambient Global State ✅ Passed The new helpers stay on String, TerminalPathResolver, and PreferredEditorService; no new file-scope mutable state or singleton namespaces were added.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 2, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes three broken terminal link behaviors: absolute path:line links silently no-opping, relative dir/file.swift:line links falling through to the browser omnibar, and single bare paths continuing to work. The fix adds a :line[:column] suffix splitter, a TerminalFileReference struct that encodes line/column as a URL fragment, a new resolveOpenURLFileReference resolver that probes literal paths before stripped ones, and goto-flag dispatch in PreferredEditorService for VS Code-family CLIs.

  • String.splitTerminalPathLineSuffix() (CmuxTerminalCore) — peels a trailing ASCII-only :line / :line:column suffix; isASCIIDigit correctly excludes Unicode numerals.
  • TerminalPathResolver.resolveOpenURLFileReference — replaces resolveOpenURLFilePath at the call site; relaxes the scheme filter from "scheme == nil" to "not http/https" so Foundation's misparse of App.swift:42 no longer kills relative links.
  • PreferredEditorService.editorInvocation — tokenises the configured command with a small shell-word splitter and passes path:line:column -g to VS Code-family editors; handles quoted and backslash-escaped binary paths.

Confidence Score: 5/5

The change is well-scoped with full package-level test coverage; bare-path links are unaffected; only the full-app Swift 6 build needs maintainer confirmation.

All four new primitives have dedicated unit tests covering edge cases. The only unverified piece is the app-target build under Swift 6 strict isolation, noted in a previous thread.

Sources/GhosttyTerminalView.swift — the @mainactor call site needs a full-app build confirmation with the Zig/Ghostty toolchain before merge.

Important Files Changed

Filename Overview
Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/PathResolution/TerminalPathResolver.swift Introduces resolveOpenURLFileReference and TerminalFileReference; refactors path-probe logic into standardizedExistingPath; scheme filter correctly relaxed to http/https-only to fix relative-path parsing.
Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/FileOpen/PreferredEditorService.swift Adds editorInvocation (internal, tested via @testable), commandTokens shell-word splitter, and lineColumn fragment decoder; goto-flag and path:line:column argument construction is correct.
Sources/GhosttyTerminalView.swift Switches to resolveOpenURLFileReference; routes line-referenced files directly to PreferredEditorService; the (true, nil) return is safe since routed==true causes early return before normalizedOpenURLString is consumed.
Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/PathResolution/String+TerminalPathTokens.swift Adds splitTerminalPathLineSuffix() with correct isASCIIDigit guard; fileprivate-scoped and well-tested.

Reviews (4): Last reviewed commit: "Fail closed on malformed line-reference ..." | Re-trigger Greptile

Comment on lines +131 to +136
guard !lastSuffix.isEmpty,
lastSuffix.allSatisfy(\.isNumber),
let lastNumber = Int(lastSuffix),
lastNumber > 0 else {
return nil
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 isNumber includes non-ASCII numeral characters

Character.isNumber returns true for Unicode numeric glyphs beyond ASCII digits — vulgar fractions (½), superscripts (²), Devanagari digits (५), etc. The guard still behaves correctly overall because Int(lastSuffix) returns nil for any non-ASCII numeral and the outer guard fails, but the predicate passes for a moment before Int() rejects the value, making the intent harder to read. Use allSatisfy({ $0.isASCII && $0.isNumber }) (or the range comparison "0"..."9") to express "only decimal digits" precisely in both calls in this function.

Comment on lines +162 to 198
private static func commandTokens(_ command: String) -> [String] {
var tokens: [String] = []
var current = ""
var quote: Character?
var hasToken = false

for character in command {
if let active = quote {
if character == active {
quote = nil
} else {
current.append(character)
}
hasToken = true
continue
}
if character == "'" || character == "\"" {
quote = character
hasToken = true
continue
}
if character.isWhitespace {
if hasToken {
tokens.append(current)
current = ""
hasToken = false
}
continue
}
current.append(character)
hasToken = true
}
if hasToken {
tokens.append(current)
}
return tokens
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Backslash-escaped spaces silently break goto navigation

commandTokens handles quoted binary paths (single and double quotes) but explicitly skips backslash-escape interpretation. A user who configures their editor as /Applications/Visual\ Studio\ Code.app/Contents/Resources/app/bin/code (backslash-escaped spaces instead of quotes) would have the command split at each unescaped space, resulting in tokens.first being "Visual\\" — which is not in gotoEditorCommandNames. The file opens fine (the bare path is used), but goto navigation silently fails: the file opens at the top rather than at the referenced line. The code comment acknowledges this, but there is no test covering the degradation. A recognizesBackslashEscapedBinaryPath test (expecting gotoFlag == "" and argument == path) would document the boundary precisely and prevent a future fix from accidentally enabling partial tokenization.

Comment on lines +3056 to +3062
if reference.line != nil {
#if DEBUG
cmuxDebugLog("link.openURL resolvedAsFileReference=\(reference.fileURL)")
#endif
PreferredEditorService(defaults: .standard).open(reference.fileURL)
return (true, nil)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 App-level actor isolation unverified — call site needs full build confirmation

PreferredEditorService.open is @MainActor, and it is called here synchronously inside the performOnMain closure. The package-level tests pass, but the PR description explicitly notes the full app target was not built locally. Swift 6 strict isolation checking happens at compile time in the app target; if performOnMain's closure type does not carry a @MainActor annotation (or an equivalent @isolated(any) parameter) the compiler may reject this call. The existing closure already calls CommandClickFileOpenRouter.shouldRouteInCmux and other presumably @MainActor symbols without issues, which is a positive signal, but a maintainer with the full toolchain should confirm the app target builds without isolation errors before merging.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/PathResolution/TerminalPathResolver.swift`:
- Around line 169-191: Extract the shared token-to-standardized-path resolution
logic out of TerminalPathResolver so the duplicated pipeline in
existingFileReference and resolveQuicklookPath is centralized. Create a small
helper that handles expandingTildeInPath, the absolute-vs-cwd path decision,
standardizingPath, and seenPaths dedup, then have existingFileReference call it
and keep only the fileExists/TerminalFileReference creation step; leave
resolveQuicklookPath as a follow-up if needed. Use the existing
existingFileReference and resolveQuicklookPath symbols to keep the refactor
scoped and consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 85da9610-ff35-418b-bf5b-0ad9e649d366

📥 Commits

Reviewing files that changed from the base of the PR and between eecc299 and 8abaaa9.

📒 Files selected for processing (6)
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/PathResolution/String+TerminalPathTokens.swift
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/PathResolution/TerminalPathResolver.swift
  • Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/TerminalPathLineReferenceTests.swift
  • Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/FileOpen/PreferredEditorService.swift
  • Packages/macOS/CmuxWorkspaces/Tests/CmuxWorkspacesTests/FileOpen/PreferredEditorLineReferenceTests.swift
  • Sources/GhosttyTerminalView.swift

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

4 issues found across 6 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/FileOpen/PreferredEditorService.swift">

<violation number="1" location="Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/FileOpen/PreferredEditorService.swift:127">
P2: Goto handling silently fails when the configured editor command includes a wrapper prefix (e.g., `env VAR=1 code`, `arch -arm64 code`, `nohup code`). The tokenizer produces the correct word list, but the code only inspects `tokens.first` to decide whether the editor supports goto arguments. Because the first token is the wrapper executable, the lookup against `gotoEditorCommandNames` misses the actual editor name and the function falls back to a plain file path, discarding the `#L...` line/column fragment. A safer approach is to scan all tokens for a recognized editor name rather than only the first one.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

…, dedup

- `splitTerminalPathLineSuffix` now requires ASCII digits, so a Unicode
  numeral suffix (`²`, full-width `42`, Devanagari) is not read as a line
  number (previously relied on `Int()` rejecting it downstream).
- `commandTokens` interprets backslash escapes, so an editor command with a
  backslash-escaped binary path (`/Applications/Visual\ Studio\ Code.app/…`)
  is still recognized as a goto editor and gets `-g` + `path:line:column`.
- Extract the shared `~`-expand / cwd-join / standardize / dedup step into
  `standardizedExistingPath`, used by both `resolveQuicklookPath` and the new
  file-reference resolver (removes the duplicated candidate pipeline).

Not changed: goto detection still keys off the command's first shell word, so
a wrapper prefix (`env VAR=1 code`, `arch -arm64 code`) opens the file without
`-g` — same first-word model as the configured-editor command elsewhere;
wrapper-prefixed editor commands are uncommon and degrade to a plain open.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/PathResolution/TerminalPathResolver.swift (1)

147-155: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Don’t exclude extensionless file references from open-url resolution.

Line 153 rejects bare extensionless files, so Makefile:12, Dockerfile:7, or README:3 return nil even when the file exists and can still fall through to browser routing. Prefer an authoritative regular-file probe for the stripped candidate, rather than using slash/extension shape as the deciding signal. As per path instructions, correctness-critical routing should resolve from a trusted computation like “resolved & exists,” not string heuristics.

Possible direction
-    private let fileExists: `@Sendable` (String) -> Bool
+    private let fileStatus: `@Sendable` (String) -> FileStatus

+    private enum FileStatus: Sendable {
+        case missing
+        case file
+        case directory
+    }

Use fileStatus so host:port can still fail closed when it resolves to a directory, while real extensionless files can open at the requested line.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/PathResolution/TerminalPathResolver.swift`
around lines 147 - 155, The open-url path check in TerminalPathResolver’s
looksLikeFilePath is too restrictive because it rejects extensionless files like
Makefile, Dockerfile, and README even when they exist. Update the resolution
flow to use a trusted regular-file existence check on the stripped candidate
(for example via fileStatus) instead of relying on slash or extension
heuristics, so host:port cases still fail closed when they resolve to a
directory while real extensionless files can still open at the requested line.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/PathResolution/TerminalPathResolver.swift`:
- Around line 147-155: The open-url path check in TerminalPathResolver’s
looksLikeFilePath is too restrictive because it rejects extensionless files like
Makefile, Dockerfile, and README even when they exist. Update the resolution
flow to use a trusted regular-file existence check on the stripped candidate
(for example via fileStatus) instead of relying on slash or extension
heuristics, so host:port cases still fail closed when they resolve to a
directory while real extensionless files can still open at the requested line.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b62736f5-2734-418b-97a1-c7000b59f319

📥 Commits

Reviewing files that changed from the base of the PR and between 8abaaa9 and 9cec0db.

📒 Files selected for processing (5)
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/PathResolution/String+TerminalPathTokens.swift
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/PathResolution/TerminalPathResolver.swift
  • Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/TerminalPathLineReferenceTests.swift
  • Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/FileOpen/PreferredEditorService.swift
  • Packages/macOS/CmuxWorkspaces/Tests/CmuxWorkspacesTests/FileOpen/PreferredEditorLineReferenceTests.swift

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 5 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Eridanus117 and others added 4 commits July 2, 2026 11:31
`commandTokens` interprets a backslash as escaping the next character even
inside double quotes, so `code "\--goto"` tokenizes to `--goto` and the
invocation drops the `-g` flag. But `/bin/sh -c` (which actually runs the
command) preserves a literal backslash inside double quotes, passing
`\--goto`. This test pins the correct behavior — `-g` must still be added —
and is red against the current tokenizer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Match `/bin/sh -c` word-splitting: a backslash is literal inside single or
double quotes and only escapes the next character when unquoted. Previously
the escape fired inside double quotes too, so `code "\--goto"` tokenized to
`--goto` and the invocation wrongly saw an existing goto flag, dropping the
`-g` that a `path:line` link needs. Unquoted backslash-escaped binary paths
(e.g. `/Applications/Visual\ Studio\ Code.app/…`) are unaffected — the escape
still applies there. Turns the prior commit's test green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`editorInvocation` keys the goto-editor check off only the command's first
shell word, so a realistic wrapper prefix (`arch -arm64 code` on Apple
Silicon, `env VAR=1 code`, `nohup code`) is not recognized as a VS Code-family
editor and the file opens without `-g` — no line jump. This test pins the
expected `-g` and is red against the first-word check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Recognize a VS Code-family editor anywhere in the configured command so a
wrapper prefix (`arch -arm64 code`, `env VAR=1 code`, `nohup code`) still
gets `-g` + `path:line:column`. The first-word-only check missed these and
degraded to a plain open with no line jump. Turns the prior commit's test
green.

The only cost is that a non-goto editor command whose bare argument is
literally named `code`/`cursor`/… would be treated as goto-capable, but an
editor command is `<editor> [flags]` (flags start with `-`), so a bare
same-named argument is not a real configuration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Eridanus117

Copy link
Copy Markdown
Contributor Author

Review round 2

Pushed f17c2cc2d addressing the two remaining open bot findings, both as red→green pairs:

  • Backslash inside double quotes misread as --goto (cubic) — commandTokens now interprets \ as an escape only when unquoted, matching how /bin/sh -c word-splits the command that's actually run. So code "\--goto" tokenizes to \--goto (not --goto) and -g is still added. 46e572470 + test 5dcaa445f.
  • Wrapper-prefixed goto editor drops -g (cubic) — editorInvocation now scans every shell word (by lastPathComponent) for a recognized VS Code-family editor instead of only the first, so arch -arm64 code / env VAR=1 code / nohup code still jump to the line. f17c2cc2d + test 332ad5a1d.

Package tests green on Xcode 26.5: CmuxWorkspaces 109, CmuxTerminalCore 188.

Re: greptile's app-target actor-isolation note — I built the full app target locally (Xcode 26.5, plus a pinned zig 0.15.2 for the Ghostty CLI helper). The changed Swift — including the @MainActor PreferredEditorService.open call site in GhosttyTerminalView.swift — type-checks and compiles clean; only the native link step failed, from a local toolchain issue in the zig-built ghostty CLI helper (undefined symbol _sigaction/_waitpid/… not linking libSystem), unrelated to this PR. This round's change is confined to the CmuxWorkspaces package and doesn't touch that call site. The three repro tokens (/abs/readme.md:1, relative dir/App.swift:1, foo.swift:42:5) still want a confirm on CI / a clean toolchain.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/FileOpen/PreferredEditorService.swift (1)

143-155: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Require the L marker and reject malformed columns.

#42, #L42:, and #L42:abc currently become line-only references, which can open the wrong location. The fragment contract is #L<line>[:<column>]; fail closed when the marker is missing or a supplied column is invalid.

As per path instructions, correctness-critical open/dispatch logic should use a single reliable signal and avoid best-effort guessing when it can route to the wrong target. <path_instructions>

Proposed fix
-        if raw.first == "L" || raw.first == "l" {
-            raw.removeFirst()
-        }
+        guard raw.first == "L" || raw.first == "l" else {
+            return nil
+        }
+        raw.removeFirst()
         let parts = raw.split(separator: ":", maxSplits: 1, omittingEmptySubsequences: false)
         guard let linePart = parts.first,
               let line = Int(linePart),
               line > 0 else {
             return nil
         }
-        if parts.count == 2, let column = Int(parts[1]), column > 0 {
+        if parts.count == 2 {
+            guard let column = Int(parts[1]), column > 0 else {
+                return nil
+            }
             return (line, column)
         }
         return (line, nil)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/FileOpen/PreferredEditorService.swift`
around lines 143 - 155, The fragment parser in PreferredEditorService’s
line/column handling is too permissive and accepts malformed references like
missing the required L marker or invalid columns, which can route to the wrong
location. Update the parsing logic that splits the fragment into line and column
so it only succeeds for the exact `#L`<line>[:<column>] form, returning nil when
the marker is absent, the line is invalid, or a supplied column is malformed or
nonpositive. Keep the behavior localized to the existing fragment parsing path
in PreferredEditorService so the open/dispatch flow uses one reliable signal
instead of best-effort guessing.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/FileOpen/PreferredEditorService.swift`:
- Around line 143-155: The fragment parser in PreferredEditorService’s
line/column handling is too permissive and accepts malformed references like
missing the required L marker or invalid columns, which can route to the wrong
location. Update the parsing logic that splits the fragment into line and column
so it only succeeds for the exact `#L`<line>[:<column>] form, returning nil when
the marker is absent, the line is invalid, or a supplied column is malformed or
nonpositive. Keep the behavior localized to the existing fragment parsing path
in PreferredEditorService so the open/dispatch flow uses one reliable signal
instead of best-effort guessing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 47946bfd-ac66-471a-b018-c4487c132e4e

📥 Commits

Reviewing files that changed from the base of the PR and between 9cec0db and 46e5724.

📒 Files selected for processing (2)
  • Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/FileOpen/PreferredEditorService.swift
  • Packages/macOS/CmuxWorkspaces/Tests/CmuxWorkspacesTests/FileOpen/PreferredEditorLineReferenceTests.swift

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/FileOpen/PreferredEditorService.swift (1)

152-161: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject malformed column fragments instead of falling back to line-only.

When the fragment contains a colon but the column is empty/invalid (L42:, L42:abc, L42:0), Line 158 falls through and returns (line, nil), so a malformed location still opens at a guessed line. Fail closed for malformed line-reference transport.

Proposed fix
-        if parts.count == 2, let column = Int(parts[1]), column > 0 {
-            return (line, column)
+        if parts.count == 2 {
+            guard let column = Int(parts[1]), column > 0 else {
+                return nil
+            }
+            return (line, column)
         }
         return (line, nil)

As per path instructions, correctness-critical path[:line[:column]] routing should avoid best-effort parsing fallbacks and fail closed when the reliable signal is missing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/FileOpen/PreferredEditorService.swift`
around lines 152 - 161, The parsing in PreferredEditorService’s line-reference
helper currently falls back to returning a line-only location when a colon is
present but the column is missing or invalid. Update the parser so that any
fragment containing a colon must only succeed when the column is present and a
valid positive integer; otherwise return nil instead of `(line, nil)`. Keep the
change localized to the raw split/Int parsing logic in the line-reference
parsing path.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/FileOpen/PreferredEditorService.swift`:
- Around line 152-161: The parsing in PreferredEditorService’s line-reference
helper currently falls back to returning a line-only location when a colon is
present but the column is missing or invalid. Update the parser so that any
fragment containing a colon must only succeed when the column is present and a
valid positive integer; otherwise return nil instead of `(line, nil)`. Keep the
change localized to the raw split/Int parsing logic in the line-reference
parsing path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 51c9c48e-8472-44e1-a807-4e55980c02bd

📥 Commits

Reviewing files that changed from the base of the PR and between 46e5724 and f17c2cc.

📒 Files selected for processing (2)
  • Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/FileOpen/PreferredEditorService.swift
  • Packages/macOS/CmuxWorkspaces/Tests/CmuxWorkspacesTests/FileOpen/PreferredEditorLineReferenceTests.swift

Eridanus117 and others added 2 commits July 2, 2026 12:02
`lineColumn(fromFragment:)` accepts fragments outside the `L<line>[:<column>]`
contract: a bare `manaflow-ai#42` (no `L` marker) becomes line 42, and a colon group with
a missing/invalid/nonpositive column (`L42:`, `L42:abc`, `L42:0`) silently
falls back to line-only. These tests pin fail-closed behavior and are red
against the current permissive parser.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Parse only the exact `L<line>[:<column>]` fragment contract that
`TerminalFileReference.fileURL` emits: require the `L` marker (so a bare
`manaflow-ai#42` anchor is not read as a line) and require a positive-integer column
when a colon group is present (so `L42:`, `L42:abc`, `L42:0` are rejected
rather than silently opening at line-only). Matches the repo's fail-closed
convention for open/dispatch routing. Turns the prior commit's tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Eridanus117

Copy link
Copy Markdown
Contributor Author

Review round 3 (CodeRabbit)

Addressed both fail-closed findings on lineColumn(fromFragment:) in c05260e00 (red test 970e98f8c → green fix c05260e00):

  • Require the L marker — a bare #42 (e.g. a document anchor) is no longer guessed into a line jump.
  • Require a positive-integer column when a colon group is present — L42:, L42:abc, L42:0 now return nil instead of falling back to line-only.

The parser now accepts exactly the L<line>[:<column>] contract that TerminalFileReference.fileURL emits.

For context: this is defensive hardening rather than a live bug — the only fragment-bearing caller today is the terminal path:line handler (reference.fileURL, always well-formed); the QuickLook/word-under-cursor opens pass a fragment-free URL(fileURLWithPath:). But it matches the repo's fail-closed open/dispatch convention and makes the decoder exactly mirror the encoder, which also keeps the documented word-under-cursor line-ref follow-up honest.

Package tests: CmuxWorkspaces 111 green (Xcode 26.5).

@Eridanus117

Copy link
Copy Markdown
Contributor Author

@lawrencecchen

@Eridanus117

Copy link
Copy Markdown
Contributor Author

@austinywang

@Eridanus117

Copy link
Copy Markdown
Contributor Author

@teamleaderleo

Copy link
Copy Markdown
Collaborator

You had path:line handling first! Main now carries the behavior in 8616849, so I’m closing this superseded PR :)

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.

2 participants