What should it do?
A process Tinycast starts should be identifiable as Tinycast's in Activity Monitor. Right now every one of them is an anonymous node row that looks exactly like everyone else's node.
The hierarchy is already correct, so this is narrower than it sounds. Every spawn in the app goes through Process().run() (MCPStdioTransport, ExtensionInstaller, ShellCommandRunner, ExtensionNodeShims, ToolRunner, CodexAppServerClient), so the child's ppid really is Tinycast and View > All Processes, Hierarchically already nests it. That just isn't the default view, and in the flat one there's nothing to go on.
What Activity Monitor shows for a non-bundled executable is p_comm, which the kernel sets from the basename of the file that was exec'd. I measured the three obvious ways to influence it, reading the field back with ps -o ucomm=:
| approach |
resulting p_comm |
symlink named tinycast-node |
node, the kernel resolves the link before setting it |
process.title = "…" |
node, that rewrites argv, which only ps -o args reads |
hard link named Tinycast (node) |
Tinycast (node) |
So the hard link is the one that works. Same inode, no disk cost, and the code signature is untouched because it lives in the Mach-O rather than in the filename. Make it once under Application Support and exec the link instead of the resolved interpreter path.
Three things I'd suggest, smallest first:
- Hard-link the interpreter for the processes Tinycast keeps alive, which really means MCP servers and the installer's node. Not every
execFile an extension makes.
- Keep tagging argv[0] and the environment.
ExecutableLocator already passes tinycast-locator as argv[0] and sets TINYCAST=1 for its login-shell lookup. That's the right instinct and it makes pgrep work, it just never reaches Activity Monitor.
- Spawn the resolved binary, not a wrapper. This is the only case where the tree genuinely breaks: anything reached through something that exits (
npx, a shell script that starts a server and returns) leaves the long-lived process reparented to launchd at ppid 1 with no link to Tinycast at all. ExecutableLocator already resolves to a real path, so this is mostly about making sure that path is what gets used.
Honest limits, so nobody is surprised later:
p_comm truncates hard at 16 characters. Tinycast (node) is 15 and fits. Tinycast Extension Host came back as Tinycast Extensi.
- Hard links can't cross volumes. node under
~/.local/share/mise and Application Support under ~/Library are normally the same volume, but that isn't guaranteed, so it needs a fallback to the plain path rather than a hard failure.
- The link goes stale when node is upgraded, since the inode changes. It has to be remade whenever the resolved path or its inode moves.
- Only the top process gets the name.
npm and npx shebangs re-exec the real node, so a grandchild is back to node.
One caveat on my own evidence: I read p_comm via ps -o ucomm=, which is the same field Activity Monitor displays for non-bundled executables. I did not open Activity Monitor and look, so if that distinction matters to you it's worth a ten-second check before any of this gets built.
Why does it belong in Tinycast?
Tinycast runs third-party extension code, and that code can spawn processes. When one of them leaks or pins a core, what the user sees is an unattributed node with no way to trace it back, not to Tinycast and certainly not to the extension that started it. An app whose whole job is running code it didn't write should make what it started legible. That's the argument, and it doesn't depend on anyone else having the feature.
It also closes a gap the project already cares about. The PR template requires a memory table and asks for it to be back to baseline after the palette closes. Processes Tinycast spawned don't appear anywhere in that accounting today, so the table is quietly less true than it reads. Making them identifiable is what lets anyone check it.
And the instinct is already in the codebase. ExecutableLocator tags argv[0] and the environment so its own shell lookup is recognisable. This is the same idea carried to the place a user actually looks.
The weight is low: a hard link and a changed exec path. No new process, no dependency, no UI, nothing to configure.
Contribution
I've already measured all of the above, so I know which approach survives contact. Happy to scope it to just (1) and (3) for the long-lived processes and leave extension execFile alone, which I think is the right size for it. Tell me if you'd rather it went differently.
What should it do?
A process Tinycast starts should be identifiable as Tinycast's in Activity Monitor. Right now every one of them is an anonymous
noderow that looks exactly like everyone else's node.The hierarchy is already correct, so this is narrower than it sounds. Every spawn in the app goes through
Process().run()(MCPStdioTransport,ExtensionInstaller,ShellCommandRunner,ExtensionNodeShims,ToolRunner,CodexAppServerClient), so the child's ppid really is Tinycast andView > All Processes, Hierarchicallyalready nests it. That just isn't the default view, and in the flat one there's nothing to go on.What Activity Monitor shows for a non-bundled executable is
p_comm, which the kernel sets from the basename of the file that was exec'd. I measured the three obvious ways to influence it, reading the field back withps -o ucomm=:p_commtinycast-nodenode, the kernel resolves the link before setting itprocess.title = "…"node, that rewrites argv, which onlyps -o argsreadsTinycast (node)Tinycast (node)So the hard link is the one that works. Same inode, no disk cost, and the code signature is untouched because it lives in the Mach-O rather than in the filename. Make it once under Application Support and exec the link instead of the resolved interpreter path.
Three things I'd suggest, smallest first:
execFilean extension makes.ExecutableLocatoralready passestinycast-locatoras argv[0] and setsTINYCAST=1for its login-shell lookup. That's the right instinct and it makespgrepwork, it just never reaches Activity Monitor.npx, a shell script that starts a server and returns) leaves the long-lived process reparented to launchd at ppid 1 with no link to Tinycast at all.ExecutableLocatoralready resolves to a real path, so this is mostly about making sure that path is what gets used.Honest limits, so nobody is surprised later:
p_commtruncates hard at 16 characters.Tinycast (node)is 15 and fits.Tinycast Extension Hostcame back asTinycast Extensi.~/.local/share/miseand Application Support under~/Libraryare normally the same volume, but that isn't guaranteed, so it needs a fallback to the plain path rather than a hard failure.npmandnpxshebangs re-exec the real node, so a grandchild is back tonode.One caveat on my own evidence: I read
p_commviaps -o ucomm=, which is the same field Activity Monitor displays for non-bundled executables. I did not open Activity Monitor and look, so if that distinction matters to you it's worth a ten-second check before any of this gets built.Why does it belong in Tinycast?
Tinycast runs third-party extension code, and that code can spawn processes. When one of them leaks or pins a core, what the user sees is an unattributed
nodewith no way to trace it back, not to Tinycast and certainly not to the extension that started it. An app whose whole job is running code it didn't write should make what it started legible. That's the argument, and it doesn't depend on anyone else having the feature.It also closes a gap the project already cares about. The PR template requires a memory table and asks for it to be back to baseline after the palette closes. Processes Tinycast spawned don't appear anywhere in that accounting today, so the table is quietly less true than it reads. Making them identifiable is what lets anyone check it.
And the instinct is already in the codebase.
ExecutableLocatortags argv[0] and the environment so its own shell lookup is recognisable. This is the same idea carried to the place a user actually looks.The weight is low: a hard link and a changed exec path. No new process, no dependency, no UI, nothing to configure.
Contribution
approvedI've already measured all of the above, so I know which approach survives contact. Happy to scope it to just (1) and (3) for the long-lived processes and leave extension
execFilealone, which I think is the right size for it. Tell me if you'd rather it went differently.