Problem
The sidecar's JS-vs-WASM entrypoint classifier (resolve_javascript_command_entrypoint in crates/sidecar/src/execution.rs) decides which embedded runtime (V8 vs WASM) runs an agentOS package entrypoint by file extension: within the /opt/agentos package mount, .wasm → WASM, everything else → JavaScript.
This does not match Linux execve semantics, which classify by content (magic bytes / shebang), never by extension:
#! → run the named interpreter
\0asm (00 61 73 6d) → WebAssembly
\x7fELF → native ELF
The extension heuristic is only correct today because agentOS package bins happen to be exclusively JS or .wasm. It would misclassify an extensionless .wasm binary, or a non-JS script, that content sniffing would get right.
Why it is currently extension-based (context)
This was a deliberate shortcut taken while making /opt/agentos guest-native. The prior content-based path (resolve_javascript_command_entrypoint_inner → load_executable_script_preview) reads the host filesystem, but guest-native tar-mounted package content is never materialized on the host — so the shebang read always failed and fell through to the WASM branch (the original misroute that surfaced as an ENOEXEC/wrong-runtime launch). Reading the bytes guest-native needs vm.kernel.read_file (&mut self), while the classifier chain is &VmState and the sibling child-process path (resolve_javascript_child_process_execution) is &self / &VmState — threading &mut cascades through several signatures. The extension rule avoided that cascade.
Note the kernel layer already IS content-based / Linux-faithful: resolve_spawn_command → parse_shebang_command (crates/kernel/src/kernel.rs) reads the file, parses the #! line, and returns ENOEXEC on a bad one. Only the sidecar's JS-vs-WASM pre-classification is extension-based.
Proposed fix
Classify by content via the kernel VFS instead of by extension:
- leading bytes
\0asm (00 61 73 6d) → WASM
- leading
#! → defer to the shebang interpreter (as the kernel already does)
- otherwise → JS
This requires a guest-side read in the classifier — either thread &mut VmState through the classifier chain (and the child-process path), or add an immutable VFS "peek" that reads the first N bytes without requiring &mut.
Location
crates/sidecar/src/execution.rs — resolve_javascript_command_entrypoint (the extension fast-path within the agentOS package mount) and its _inner shebang fallback, which currently reads the host path rather than the guest VFS.
Problem
The sidecar's JS-vs-WASM entrypoint classifier (
resolve_javascript_command_entrypointincrates/sidecar/src/execution.rs) decides which embedded runtime (V8 vs WASM) runs an agentOS package entrypoint by file extension: within the/opt/agentospackage mount,.wasm→ WASM, everything else → JavaScript.This does not match Linux
execvesemantics, which classify by content (magic bytes / shebang), never by extension:#!→ run the named interpreter\0asm(00 61 73 6d) → WebAssembly\x7fELF→ native ELFThe extension heuristic is only correct today because agentOS package bins happen to be exclusively JS or
.wasm. It would misclassify an extensionless.wasmbinary, or a non-JS script, that content sniffing would get right.Why it is currently extension-based (context)
This was a deliberate shortcut taken while making
/opt/agentosguest-native. The prior content-based path (resolve_javascript_command_entrypoint_inner→load_executable_script_preview) reads the host filesystem, but guest-native tar-mounted package content is never materialized on the host — so the shebang read always failed and fell through to the WASM branch (the original misroute that surfaced as anENOEXEC/wrong-runtime launch). Reading the bytes guest-native needsvm.kernel.read_file(&mut self), while the classifier chain is&VmStateand the sibling child-process path (resolve_javascript_child_process_execution) is&self/&VmState— threading&mutcascades through several signatures. The extension rule avoided that cascade.Note the kernel layer already IS content-based / Linux-faithful:
resolve_spawn_command→parse_shebang_command(crates/kernel/src/kernel.rs) reads the file, parses the#!line, and returnsENOEXECon a bad one. Only the sidecar's JS-vs-WASM pre-classification is extension-based.Proposed fix
Classify by content via the kernel VFS instead of by extension:
\0asm(00 61 73 6d) → WASM#!→ defer to the shebang interpreter (as the kernel already does)This requires a guest-side read in the classifier — either thread
&mut VmStatethrough the classifier chain (and the child-process path), or add an immutable VFS "peek" that reads the first N bytes without requiring&mut.Location
crates/sidecar/src/execution.rs—resolve_javascript_command_entrypoint(the extension fast-path within the agentOS package mount) and its_innershebang fallback, which currently reads the host path rather than the guest VFS.