From 6d180a876acd235786931056be03e3f09da136e0 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Thu, 30 Apr 2026 22:42:24 +0200 Subject: [PATCH] resolve bare command names via PATH before execve MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit execve doesn't search PATH — bare names like 'tmux' or 'claude' fail silently. Resolve via the environment's PATH in Dart before passing to the child. This matches ptyc's old execvp behavior. Co-Authored-By: Claude Opus 4.6 (1M context) --- .pql/pql-plan.json | 2 +- lib/src/pty/native_pty.dart | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.pql/pql-plan.json b/.pql/pql-plan.json index 741bf004..0e39e917 100644 --- a/.pql/pql-plan.json +++ b/.pql/pql-plan.json @@ -1,5 +1,5 @@ { - "exported_at": "2026-04-30T18:30:16Z", + "exported_at": "2026-04-30T20:42:24Z", "decisions": [ { "id": "D-1", diff --git a/lib/src/pty/native_pty.dart b/lib/src/pty/native_pty.dart index 026584c4..e03153bb 100644 --- a/lib/src/pty/native_pty.dart +++ b/lib/src/pty/native_pty.dart @@ -11,7 +11,7 @@ library; import 'dart:async'; import 'dart:ffi' as ffi; -import 'dart:io' show Platform; +import 'dart:io' show File, Platform; import 'dart:isolate'; import 'dart:typed_data'; @@ -120,6 +120,19 @@ class NativePty { String? workingDirectory, Map environment = const {}, }) { + // Resolve bare command names via PATH (execve doesn't search PATH). + if (!executable.contains('/')) { + final path = environment['PATH'] ?? Platform.environment['PATH'] ?? ''; + for (final dir in path.split(':')) { + if (dir.isEmpty) continue; + final candidate = '$dir/$executable'; + if (File(candidate).existsSync()) { + executable = candidate; + break; + } + } + } + // Force-resolve FFI functions that run in the child process. // Top-level finals are lazy; touching them here ensures the FFI // trampolines are compiled before fork() clones the process.