Reports · Issue #4492

Image “Open in editor” can prepare a terminal directory launch

2026-09-30 · Bug · Low priority · Low effort

PARTIALLY REPRODUCED · High confidence in the tested UI, target selection and invocation preparation.

Claim, expected and actual

The issue describes an image preview action labelled “Open in editor” opening a terminal at the containing directory. Expected: either open the image in a suitable app or label the action to reflect the actual target behavior.

In both clean runs, an actual FilePreview displays a synthetic SVG and the action remains labelled “Open in editor”. Clicking it runs the real buildOpenInEditorHandler and useLocalOpenTargets hook. With the file target explicitly set to Ghostty, the hook sends the image path and Ghostty target to a controlled loopback adapter. That adapter invokes the real macOS routing implementation against a temporary image file with process execution replaced by a recorder. With no terminal editor available, the recorded invocation is open -a Ghostty /SYNTHETIC/images: the image filename is absent.

Current main has separate workspace and file preferences. Setting only the workspace preference to Ghostty does not reproduce that routing in this fixture: automatic image-file selection chooses the available default app. This qualification materially narrows the issue’s original setup. The current button label is generic; a parenthesized suffix in this source is a keyboard shortcut, not a target name.

ControlActual result in both runs
Browser: workspace Ghostty, explicit file Ghostty; no terminal editorUI label “Open in editor”; target ghostty; recorded open -a Ghostty /SYNTHETIC/images
Browser: workspace Ghostty, automatic file preferenceTarget default-app; recorded open -- /SYNTHETIC/images/synthetic.svg
Browser: workspace Ghostty, explicit default-app file preferenceSame direct image-path invocation
Adapter: text file with Ghostty, no terminal editorSame containing-directory launch as image
Adapter: image file with Ghostty, simulated Vim availabilityTerminal editor command includes both cd to the directory and vim with the image path; it is not directory-only

No real editor or terminal process was launched. The observed invocation is the actual output delivered to the injected execution boundary, not proof that macOS or Ghostty opens a window. The Vim case records a proposed command and does not execute it or establish useful image viewing.

Root cause and proposed change

The preview header uses the generic “Open in editor” label whenever a callback exists, including image state. The handler constructs an absolute path without selecting an image-specific action. The real hook resolves the preferred file target for that path. An explicit available file target wins even for an image; automatic fallback ranks native/default apps ahead of terminals for image extensions. The fixture verifies that distinction.

Terminal invocation preparation resolves the configured or available terminal editor. Ghostty launch argument construction uses the containing directory when no editor command is available, and an editor command plus file path otherwise. The tested directory-only result follows this branch; it should not be generalized to every terminal configuration.

Proposed fix: make the image action target-aware, for example “Open in Ghostty” when an explicit terminal target is selected, and offer a distinct “Open with default app” action for images. Preserve explicit target intent unless product policy deliberately changes it. Avoid describing a containing-directory action as image editing. Add integration coverage for other terminal targets, native invocation and actual image-viewing behavior. No production fix or PR was created.

Trusted base and environment

Fetched public origin/main: e52f3662fb6e52eb85238d22a07d1bac16b6e430. Linux x86_64, Node 24.19.0, pinned pnpm 9.15.0, Vite 8.0.12 optimized build, headless desktop Chromium 151.0.7922.173 at 1100×1100. The adapter runtime explicitly selects darwin behavior; this is not execution on macOS. Two clean detached checkouts at the same SHA used separate node_modules, install caches, browser profiles, loopback ports and newly created temporary image/application directories. Dependency downloads were shared. Both frozen installs and builds succeeded, with no added dependency.

Exact repeatable steps

Save the five source files below under apps/app/issue4492/ in each clean checkout, and capture.mjs outside the checkouts. Use pnpm 9.15.0 and an existing Chromium installation. Run the adapter server and Chromium in separate terminals, then capture. The server first executes four assertions against real invocation preparation and prints their actual records. Every browser click makes a fresh loopback invocation through the same implementation. Only two explicit targets and the exact synthetic image path are accepted. All process execution is intercepted; environment inputs are an empty object, not inherited user settings.

WORK=$(mktemp -d)
git clone https://github.com/get-bb/bb.git "$WORK/base"
git -C "$WORK/base" worktree add --detach "$WORK/first" e52f3662fb6e52eb85238d22a07d1bac16b6e430
git -C "$WORK/base" worktree add --detach "$WORK/second" e52f3662fb6e52eb85238d22a07d1bac16b6e430
# Save all five source files in each checkout and capture.mjs in WORK.
cd "$WORK/first"
npm_config_cache="$WORK/npm-first" npm_config_devdir="$WORK/node-gyp-first" XDG_CACHE_HOME="$WORK/cache-first" pnpm install --frozen-lockfile --store-dir "$WORK/dependency-store"
pnpm exec turbo run build --filter=@bb/app
cd apps/app
pnpm exec vite build --config issue4492/vite.config.ts
NODE_OPTIONS=--conditions=source pnpm exec tsx issue4492/serve.ts 49491
chromium --headless --no-sandbox --disable-gpu --no-first-run --disable-dev-shm-usage --remote-debugging-address=127.0.0.1 --remote-debugging-port=49492 --user-data-dir="$WORK/browser-first" about:blank
node "$WORK/capture.mjs" 49492 49491 "$WORK/first-run"
# Same-agent second clean reproduction:
cd "$WORK/second"
npm_config_cache="$WORK/npm-second" npm_config_devdir="$WORK/node-gyp-second" XDG_CACHE_HOME="$WORK/cache-second" pnpm install --frozen-lockfile --store-dir "$WORK/dependency-store"
pnpm exec turbo run build --filter=@bb/app
cd apps/app
pnpm exec vite build --config issue4492/vite.config.ts
NODE_OPTIONS=--conditions=source pnpm exec tsx issue4492/serve.ts 49493
chromium --headless --no-sandbox --disable-gpu --no-first-run --disable-dev-shm-usage --remote-debugging-address=127.0.0.1 --remote-debugging-port=49494 --user-data-dir="$WORK/browser-second" about:blank
node "$WORK/capture.mjs" 49494 49493 "$WORK/second-run"

apps/app/issue4492/index.html

<html><head><title>Synthetic image target routing</title></head><body><div id="root"></div><script type="module" src="/main.tsx"></script></body></html>

apps/app/issue4492/main.tsx

import React,{useState,useEffect} from "react";
import {createRoot} from "react-dom/client";
import {Provider,createStore,useSetAtom} from "jotai";
import {TooltipProvider} from "@bb/shared-ui/tooltip";
import {FilePreview} from "../src/components/secondary-panel/FilePreview";
import {useLocalOpenTargets} from "../src/hooks/useLocalOpenTargets";
import {buildOpenInEditorHandler} from "../src/views/thread-detail/threadWorkspaceOpenPath";
import {fileOpenTargetPreferenceAtom,workspaceOpenTargetPreferenceAtom} from "../src/lib/workspace-open-target-preference";
import "../src/app.css";
const store=createStore();store.set(workspaceOpenTargetPreferenceAtom,"ghostty");store.set(fileOpenTargetPreferenceAtom,"ghostty");
function App(){const [result,setResult]=useState(null);const [preference,setPreference]=useState("ghostty");const setFile=useSetAtom(fileOpenTargetPreferenceAtom);const targets=useLocalOpenTargets({enabled:true});useEffect(()=>{const handler=(event:any)=>setResult(event.detail);window.addEventListener("fixture-result",handler);return()=>window.removeEventListener("fixture-result",handler);},[]);const open=buildOpenInEditorHandler({rootPath:"/SYNTHETIC",canOpenPreferredTarget:targets.canOpenPreferredFileTarget,openInPreferredTarget:targets.openPathInPreferredFileTarget});return <main style={{padding:32,maxWidth:1000}}><h1>Synthetic image open-target routing</h1><p>Actual FilePreview and target-selection hook. Local controlled adapter records launch arguments; no native app is launched.</p><p>Workspace target remains Ghostty. File preference: <strong id="preference">{preference}</strong></p><nav style={{display:"flex",gap:20,margin:"16px 0"}}>{["ghostty","automatic","default-app"].map(value=><button key={value} data-preference={value} onClick={()=>{setPreference(value);setFile(value==="automatic"?null:value as never);setResult(null);}}>{value}</button>)}</nav><section style={{height:320,border:"1px solid #aaa"}}><FilePreview state={{kind:"image",url:"/fixture.svg"}} path="images/synthetic.svg" onOpenInEditor={open}/></section><pre id="result" style={{whiteSpace:"pre-wrap"}}>{JSON.stringify(result,null,2)}</pre></main>};
createRoot(document.getElementById("root")!).render(<Provider store={store}><TooltipProvider><App/></TooltipProvider></Provider>);

apps/app/issue4492/boundary.ts

export const targets = [{id:"ghostty",label:"Ghostty",kind:"terminal",icon:{kind:"builtin",name:"ghostty"},capabilities:{openDirectory:true,openFile:true,openFileAtLine:true,openFileAtColumn:true}},{id:"default-app",label:"Default App",kind:"default-app",icon:{kind:"symbol",name:"default-app"},capabilities:{openDirectory:true,openFile:true,openFileAtLine:false,openFileAtColumn:false}}];
export function useHostDaemon(){return {hasDaemon:true};}
export function useWorkspaceOpenTargets(){return {isLoading:false,workspaceOpenTargets:targets,fetchWorkspaceOpenTargetsForPath:async()=>targets,openWorkspace:async(request:any)=>{const response=await fetch("/invoke",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(request)});if(!response.ok)throw new Error("Fixture invocation failed");const result=await response.json();window.dispatchEvent(new CustomEvent("fixture-result",{detail:{request,result}}));}};}

apps/app/issue4492/vite.config.ts

import {defineConfig} from "vite";
import {resolve} from "node:path";
import {sharedViteConfig} from "../vite.config";
export default defineConfig({...sharedViteConfig,root:__dirname,plugins:[{name:"synthetic-daemon-boundary",enforce:"pre",resolveId(source,importer){if(importer?.endsWith("/hooks/useLocalOpenTargets.ts")&&["./useHostDaemon","./useWorkspaceOpenTargets"].includes(source))return resolve(__dirname,"boundary.ts");}},...sharedViteConfig.plugins.filter(plugin=>!["bb:font-preload","bb:split-prefetch"].includes(plugin.name))],build:{...sharedViteConfig.build,outDir:"dist",emptyOutDir:true}});

apps/app/issue4492/serve.ts

import {createServer} from "node:http";
import {mkdtemp,mkdir,writeFile,readFile,stat} from "node:fs/promises";
import {tmpdir} from "node:os";
import path from "node:path";
import assert from "node:assert/strict";
import {openPathInTargetWithRuntime} from "../../../packages/local-open-targets/src/index";
const port=Number(process.argv[2]);assert(Number.isInteger(port)&&port>1024);
const root=await mkdtemp(path.join(tmpdir(),"bb-image-fixture-"));
const svg='<svg xmlns="http://www.w3.org/2000/svg" width="480" height="180"><rect width="480" height="180" fill="#dceeff"/><circle cx="90" cy="90" r="50" fill="#3478bc"/><text x="165" y="98" font-family="sans-serif" font-size="24">Synthetic image</text></svg>';
await mkdir(path.join(root,"images"));await mkdir(path.join(root,"Applications","Ghostty.app"),{recursive:true});await writeFile(path.join(root,"images","synthetic.svg"),svg);await writeFile(path.join(root,"images","control.txt"),"Synthetic text control");
const normalize=(s:string)=>s.replaceAll(root,"/SYNTHETIC");
async function invoke(targetId:string,filename="synthetic.svg",editor=false){const calls:{file:string,args:string[]}[]=[];await openPathInTargetWithRuntime({context:{kind:"local"},targetId:targetId as never,path:path.join(root,"images",filename),lineNumber:null,columnNumber:null},{platform:"darwin",applicationDirectories:[],desktopFileDirectories:[],env:{},execFile:async(file,args)=>{if(file==="mdfind")return {stdout:args.join(" ").includes("com.mitchellh.ghostty")?path.join(root,"Applications","Ghostty.app")+"\n":""};if(file==="which"){if(editor&&args[0]==="vim")return {stdout:"/synthetic-bin/vim\n"};throw new Error("No synthetic executable");}if(file==="open"){calls.push({file,args:args.map(normalize)});return {stdout:""};}throw new Error("Unexpected fixture adapter probe: "+file);}});return calls;}
const selfChecks={imageTerminal:await invoke("ghostty"),imageDefault:await invoke("default-app"),textTerminal:await invoke("ghostty","control.txt"),imageTerminalWithEditor:await invoke("ghostty","synthetic.svg",true)};
assert.deepEqual(selfChecks.imageTerminal,[{file:"open",args:["-a","Ghostty","/SYNTHETIC/images"]}]);assert.deepEqual(selfChecks.imageDefault,[{file:"open",args:["--","/SYNTHETIC/images/synthetic.svg"]}]);assert.deepEqual(selfChecks.textTerminal,selfChecks.imageTerminal);assert(selfChecks.imageTerminalWithEditor[0].args.join(" ").includes("'vim' '/SYNTHETIC/images/synthetic.svg'"));console.log(JSON.stringify({selfChecks},null,2));
const dist=new URL("./dist/",import.meta.url).pathname;
createServer(async(req,res)=>{try{if(req.method==="POST"&&req.url==="/invoke"){let body="";for await(const chunk of req){body+=chunk;if(body.length>4096)throw new Error("Too large");}const data=JSON.parse(body);assert(data.path==="/SYNTHETIC/images/synthetic.svg"&&["ghostty","default-app"].includes(data.targetId)&&data.context.kind==="local"&&data.lineNumber===null&&data.columnNumber===null);const calls=await invoke(data.targetId);res.setHeader("Content-Type","application/json");res.end(JSON.stringify({calls}));return;}if(req.method!=="GET")throw new Error("Unsupported request");if(req.url==="/fixture.svg"){res.setHeader("Content-Type","image/svg+xml");res.end(svg);return;}const pathname=new URL(req.url!,"http://127.0.0.1").pathname;const file=path.resolve(dist,"."+(pathname==="/"?"/index.html":pathname));assert(file.startsWith(dist));const contents=await readFile(file);res.setHeader("Content-Type",file.endsWith(".js")?"text/javascript":file.endsWith(".css")?"text/css":file.endsWith(".html")?"text/html":"application/octet-stream");res.end(contents);}catch{res.statusCode=400;res.end("Fixture request rejected");}}).listen(port,"127.0.0.1",()=>console.log("READY"));

capture.mjs

import { writeFile } from 'node:fs/promises';
const [debugPort,appPort,output]=process.argv.slice(2);
const tabs=await(await fetch(`http://127.0.0.1:${debugPort}/json/list`)).json();
const ws=new WebSocket(tabs.find(t=>t.type==='page').webSocketDebuggerUrl);
await new Promise(r=>ws.addEventListener('open',r,{once:true}));let id=0;const pending=new Map();
ws.addEventListener('message',event=>{const x=JSON.parse(event.data);if(x.id){const p=pending.get(x.id);pending.delete(x.id);x.error?p.reject(new Error(JSON.stringify(x.error))):p.resolve(x.result);}});
const call=(method,params={})=>new Promise((resolve,reject)=>{const serial=++id;pending.set(serial,{resolve,reject});ws.send(JSON.stringify({id:serial,method,params}));});
const evaluate=async expression=>{const x=await call('Runtime.evaluate',{expression,returnByValue:true,awaitPromise:true});if(x.exceptionDetails)throw new Error(JSON.stringify(x.exceptionDetails));return x.result.value;};
await call('Page.enable');await call('Runtime.enable');await call('Network.enable');await call('Network.setCacheDisabled',{cacheDisabled:true});
await call('Emulation.setDeviceMetricsOverride',{width:1100,height:1100,deviceScaleFactor:1,mobile:false});
const errors=[];
ws.addEventListener('message',event=>{const x=JSON.parse(event.data);if(x.method==='Runtime.exceptionThrown')errors.push(x.params.exceptionDetails.text+': '+(x.params.exceptionDetails.exception?.description??''));});
await call('Page.navigate',{url:`http://127.0.0.1:${appPort}/`});
await new Promise(r=>setTimeout(r,2500));
const observations=[];
for(const preference of ['ghostty','automatic','default-app']){
 await evaluate(`document.querySelector('[data-preference="${preference}"]').click()`);await new Promise(r=>setTimeout(r,300));
 const before=await evaluate(`(()=>{const b=document.querySelector('button[aria-label="Open in editor"]');const img=document.querySelector('section img');if(!b)throw new Error('Missing editor action');const r=b.getBoundingClientRect();return {label:b.getAttribute('aria-label'),image:{complete:img.complete,naturalWidth:img.naturalWidth},x:r.x+r.width/2,y:r.y+r.height/2};})()`);
 if(!before.image.complete||before.image.naturalWidth!==480)throw new Error('Synthetic image did not load');
 await call('Input.dispatchMouseEvent',{type:'mouseMoved',x:before.x,y:before.y});await new Promise(r=>setTimeout(r,800));
 await evaluate(`document.querySelector('button[aria-label="Open in editor"]').click()`);
 let result=null;for(let attempt=0;attempt<30;attempt++){result=await evaluate('JSON.parse(document.getElementById("result").textContent)');if(result)break;await new Promise(r=>setTimeout(r,100));}
 if(!result)throw new Error('Invocation did not finish');
 const target=preference==='ghostty'?'ghostty':'default-app';if(result.request.targetId!==target||result.request.path!=='/SYNTHETIC/images/synthetic.svg')throw new Error(JSON.stringify(result));
 const expected=target==='ghostty'?['-a','Ghostty','/SYNTHETIC/images']:['--','/SYNTHETIC/images/synthetic.svg'];if(JSON.stringify(result.result.calls)!==JSON.stringify([{file:'open',args:expected}]))throw new Error(JSON.stringify(result));
 await call('Input.dispatchMouseEvent',{type:'mouseMoved',x:10,y:10});await call('Input.dispatchMouseEvent',{type:'mouseMoved',x:before.x,y:before.y});await new Promise(r=>setTimeout(r,1000));
 observations.push({preference,label:before.label,image:before.image,...result});
 const shot=await call('Page.captureScreenshot',{format:'png',captureBeyondViewport:false});await writeFile(`${output}-${preference}.png`,Buffer.from(shot.data,'base64'));
}
if(errors.length)throw new Error(JSON.stringify(errors));
const result={observations,browser:await call('Browser.getVersion'),viewport:{width:1100,height:1100},errors};await writeFile(output+'.json',JSON.stringify(result,null,2));console.log(JSON.stringify(result));ws.close();

Same-agent second clean reproduction: actual evidence

The same agent personally repeated installation, build, adapter startup/assertions and browser capture in the second clean checkout. All four adapter outcomes and all three browser records match exactly. Each browser run exited 0 with no runtime exception and verified the image loaded at naturalWidth 480, the action label, target, image path and final invocation arguments. Six genuine screenshots were inspected. Tooltip visibility differs between captures; the accessible action label is identical in both. This is a same-agent second clean reproduction, not independent verification.

first adapter output

{
  "selfChecks": {
    "imageTerminal": [
      {
        "file": "open",
        "args": [
          "-a",
          "Ghostty",
          "/SYNTHETIC/images"
        ]
      }
    ],
    "imageDefault": [
      {
        "file": "open",
        "args": [
          "--",
          "/SYNTHETIC/images/synthetic.svg"
        ]
      }
    ],
    "textTerminal": [
      {
        "file": "open",
        "args": [
          "-a",
          "Ghostty",
          "/SYNTHETIC/images"
        ]
      }
    ],
    "imageTerminalWithEditor": [
      {
        "file": "open",
        "args": [
          "-na",
          "Ghostty.app",
          "--args",
          "-e",
          "/bin/zsh",
          "-lc",
          "cd '/SYNTHETIC/images' && 'vim' '/SYNTHETIC/images/synthetic.svg'"
        ]
      }
    ]
  }
}

first browser output

{
  "observations": [
    {
      "preference": "ghostty",
      "label": "Open in editor",
      "image": {
        "complete": true,
        "naturalWidth": 480
      },
      "request": {
        "columnNumber": null,
        "context": {
          "kind": "local"
        },
        "lineNumber": null,
        "path": "/SYNTHETIC/images/synthetic.svg",
        "targetId": "ghostty"
      },
      "result": {
        "calls": [
          {
            "file": "open",
            "args": [
              "-a",
              "Ghostty",
              "/SYNTHETIC/images"
            ]
          }
        ]
      }
    },
    {
      "preference": "automatic",
      "label": "Open in editor",
      "image": {
        "complete": true,
        "naturalWidth": 480
      },
      "request": {
        "columnNumber": null,
        "context": {
          "kind": "local"
        },
        "lineNumber": null,
        "path": "/SYNTHETIC/images/synthetic.svg",
        "targetId": "default-app"
      },
      "result": {
        "calls": [
          {
            "file": "open",
            "args": [
              "--",
              "/SYNTHETIC/images/synthetic.svg"
            ]
          }
        ]
      }
    },
    {
      "preference": "default-app",
      "label": "Open in editor",
      "image": {
        "complete": true,
        "naturalWidth": 480
      },
      "request": {
        "columnNumber": null,
        "context": {
          "kind": "local"
        },
        "lineNumber": null,
        "path": "/SYNTHETIC/images/synthetic.svg",
        "targetId": "default-app"
      },
      "result": {
        "calls": [
          {
            "file": "open",
            "args": [
              "--",
              "/SYNTHETIC/images/synthetic.svg"
            ]
          }
        ]
      }
    }
  ],
  "browser": {
    "protocolVersion": "1.3",
    "product": "Chrome/151.0.7922.173",
    "revision": "@a96602f30358e9b5d256a0464e7e4d4bec223004",
    "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/151.0.0.0 Safari/537.36",
    "jsVersion": "15.1.206.23"
  },
  "viewport": {
    "width": 1100,
    "height": 1100
  },
  "errors": []
}

first: ghostty

Actual first Chromium screenshot with ghostty file preference and recorded invocation

first: automatic

Actual first Chromium screenshot with automatic file preference and recorded invocation

first: default-app

Actual first Chromium screenshot with default-app file preference and recorded invocation

second adapter output

{
  "selfChecks": {
    "imageTerminal": [
      {
        "file": "open",
        "args": [
          "-a",
          "Ghostty",
          "/SYNTHETIC/images"
        ]
      }
    ],
    "imageDefault": [
      {
        "file": "open",
        "args": [
          "--",
          "/SYNTHETIC/images/synthetic.svg"
        ]
      }
    ],
    "textTerminal": [
      {
        "file": "open",
        "args": [
          "-a",
          "Ghostty",
          "/SYNTHETIC/images"
        ]
      }
    ],
    "imageTerminalWithEditor": [
      {
        "file": "open",
        "args": [
          "-na",
          "Ghostty.app",
          "--args",
          "-e",
          "/bin/zsh",
          "-lc",
          "cd '/SYNTHETIC/images' && 'vim' '/SYNTHETIC/images/synthetic.svg'"
        ]
      }
    ]
  }
}

second browser output

{
  "observations": [
    {
      "preference": "ghostty",
      "label": "Open in editor",
      "image": {
        "complete": true,
        "naturalWidth": 480
      },
      "request": {
        "columnNumber": null,
        "context": {
          "kind": "local"
        },
        "lineNumber": null,
        "path": "/SYNTHETIC/images/synthetic.svg",
        "targetId": "ghostty"
      },
      "result": {
        "calls": [
          {
            "file": "open",
            "args": [
              "-a",
              "Ghostty",
              "/SYNTHETIC/images"
            ]
          }
        ]
      }
    },
    {
      "preference": "automatic",
      "label": "Open in editor",
      "image": {
        "complete": true,
        "naturalWidth": 480
      },
      "request": {
        "columnNumber": null,
        "context": {
          "kind": "local"
        },
        "lineNumber": null,
        "path": "/SYNTHETIC/images/synthetic.svg",
        "targetId": "default-app"
      },
      "result": {
        "calls": [
          {
            "file": "open",
            "args": [
              "--",
              "/SYNTHETIC/images/synthetic.svg"
            ]
          }
        ]
      }
    },
    {
      "preference": "default-app",
      "label": "Open in editor",
      "image": {
        "complete": true,
        "naturalWidth": 480
      },
      "request": {
        "columnNumber": null,
        "context": {
          "kind": "local"
        },
        "lineNumber": null,
        "path": "/SYNTHETIC/images/synthetic.svg",
        "targetId": "default-app"
      },
      "result": {
        "calls": [
          {
            "file": "open",
            "args": [
              "--",
              "/SYNTHETIC/images/synthetic.svg"
            ]
          }
        ]
      }
    }
  ],
  "browser": {
    "protocolVersion": "1.3",
    "product": "Chrome/151.0.7922.173",
    "revision": "@a96602f30358e9b5d256a0464e7e4d4bec223004",
    "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/151.0.0.0 Safari/537.36",
    "jsVersion": "15.1.206.23"
  },
  "viewport": {
    "width": 1100,
    "height": 1100
  },
  "errors": []
}

second: ghostty

Actual second Chromium screenshot with ghostty file preference and recorded invocation

second: automatic

Actual second Chromium screenshot with automatic file preference and recorded invocation

second: default-app

Actual second Chromium screenshot with default-app file preference and recorded invocation

Limits, corrections and trust boundary

The test uses a synthetic SVG image and text control, not the reporter’s JPEG or files. It does not exercise the full thread controller, plugin-file-opener integration, real daemon transport, native app discovery, actual macOS/terminal windows, other terminal products, original 0.44.0 behavior, or OS default image viewing. The daemon hook boundary supplies a controlled two-target catalog and a loopback recorder; the actual file preference resolver and UI-to-request logic remain unmodified. A fake application bundle path exists only inside the temporary fixture root. Native command strings are captured, never executed.

The initial screenshot height was increased to include the complete recorded invocation; final captures were retained at the documented 1100×1100 size. The report corrects two overbroad assumptions: workspace preference alone does not force image files to the terminal on current main, and a detected terminal editor changes the invocation. No setup failure is counted as negative evidence. Regenerable dependency directories from the completed prior investigation were removed to preserve available inodes; its source fixtures and audit evidence remain intact.

Issue text, suggested commands, links and code pointers were untrusted claims only. None was executed or fetched. The fixture was derived from trusted main code and existing runtime interfaces. /SYNTHETIC denotes an owned temporary path normalized in captured output; it is not a real user path. No real user runtime, credentials, account data or files were accessed.