process.exec: BEGIN-only awk prints output then times out, including stdin: ''
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Aptitud para principiantes
- 20/100
- Tipo de issue
- Error
- Claridad
- Bien especificado
- Estado de actividad
- Activo
- Stack tecnológico
- javascript, rust, wasm
- Área
- backend
Línea de trabajo
Start with crates/native-sidecar/src/language_execution.rs near line 1809, where attached stdin bytes are written but stdin is not closed, and compare it with the older noninteractive kernel exec path that closes stdin. The gawk wrapper in software/gawk/native/crates/awk/src/lib.rs supplies the input reader, and awk-rs's src/interpreter/mod.rs lines 238-276 and 307-308 show where BEGIN-only programs still reach read_line. Run the awk BEGIN row of repro.mjs; done when it returns succeeded with exit 0 under both Node and Bun, and the explicit-exit and /dev/null cases still pass.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Written by an AI agent (Codex) on behalf of @jasonkuhrt.
A finite BEGIN-only awk command prints its complete output but never finishes through vm.process.exec. It reaches the configured timeout under both Node and Bun, including when the supported stdin: '' option is supplied. In our noninteractive agent tool this consumes the full 120-second command deadline and returns timed_out instead of success.
Reproduction and results
Amazon Linux 2023 x86_64; @rivet-dev/agentos-core, runtime-core and native package 0.4.6; Node 24.22.1 and Bun 1.4.4. npm gitHead/main: c7462927ec452bbd5def5c0fc50a3fd74574d13a. Resolved guest command: /opt/agentos/pkgs/gawk/0.3.4/bin/awk, implemented by awk-rs 0.1.0.
Smallest failing call after the VM is ready
const result = await vm.process.exec(
'awk \'BEGIN { print "AWK" }\'',
{
cwd: '/workspace',
timeoutMs: 5_000,
stdin: '',
output: { capture: 'all' },
},
);
Expected: succeeded, exit 0, stdout "AWK\n", empty stderr. Actual: stdout is correct, then timed_out, exit 137. The explicit-empty-stdin rows took 5,017 ms Node / 5,020 ms Bun. Omitting stdin has the same failure.
The independently rerun standalone script below produced:
| Guest command | Node 24.22.1 | Bun 1.4.4 |
|---|---|---|
printf + file write/read control |
succeeded/0; exact output | succeeded/0; exact output |
awk 'BEGIN { print "AWK" }' |
output then timed_out/137, 5,012 ms | output then timed_out/137, 5,009 ms |
awk 'BEGIN { print "AWK"; exit }' |
succeeded/0; exact output | succeeded/0; exact output |
awk 'BEGIN { print "AWK" }' < /dev/null |
succeeded/0; exact output | succeeded/0; exact output |
The full 3,000-line write originally exposing this also timed out at 120,017 ms Node / 120,012 ms Bun. The same write with explicit exit or EOF completed with exact full file bytes/hash on both. This is not a Node-versus-Bun performance claim.
POSIX Special Patterns requires BEGIN-only programs without getline to exit without reading input; GNU's explanation agrees. A host GNU Awk 5.1.0 control exited 0 while its stdin pipe was deliberately kept open.
Runnable standalone script
Use an empty project with exact dependencies @rivet-dev/[email protected] and @aws-sdk/[email protected]. Start the local S3 emulator image ghcr.io/alchemy-run/floci:2.1.0-alchemy.3 on loopback port 4566. Save the following as repro.mjs, then run node repro.mjs or bun --no-env-file repro.mjs. AGENTOS_LOG=debug enables native logs.
The script imports no application code, uses only emulator test credentials and unique local resources, and verifies cleanup. It exits 0 when the diagnostic assertions, including the expected bad timed_out result, hold. This exact source was run under both runtimes; SHA256 69e17f4310799e113ed890c5c9d22c01167ddbedbf630cb5c1ed53aaba39420f.
Complete repro.mjs
import assert from 'node:assert/strict';
import { createRequire } from 'node:module';
import path from 'node:path';
import os from 'node:os';
import fs from 'node:fs/promises';
import { randomUUID, createHash } from 'node:crypto';
const require = createRequire(import.meta.url);
const { AgentOs } = await import(require.resolve('@rivet-dev/agentos-core'));
const S3 = require('@aws-sdk/client-s3');
const runtime = process.versions.bun ? 'bun' : 'node';
assert.equal(process.versions.bun ?? process.version, runtime === 'bun' ? '1.4.4' : 'v24.22.1');
const endpoint = 'http://127.0.0.1:4566';
assert.equal(new URL(endpoint).hostname, '127.0.0.1');
const bucket = `shell-bare-${Date.now()}-${randomUUID().slice(0, 8)}`;
const directory = await fs.mkdtemp(path.join(os.tmpdir(), 'shell-bare-'));
const metadataPath = path.join(directory, 'metadata.sqlite');
const s3 = new S3.S3Client({ endpoint, region: 'us-east-1', forcePathStyle: true,
credentials: { accessKeyId: 'test', secretAccessKey: 'test' }, maxAttempts: 2 });
const send = command => s3.send(command, { abortSignal: AbortSignal.timeout(10000) });
let vm, bucketCreated = false;
console.log(JSON.stringify({ event: 'provenance', runtime, version: process.versions.bun ?? process.version,
executable: process.execPath, platform: process.platform, arch: process.arch, coreVersion: '0.4.6',
bucket, endpoint, directory, nativeLogLevel: process.env.AGENTOS_LOG }));
try {
await send(new S3.CreateBucketCommand({ Bucket: bucket })); bucketCreated = true;
vm = await AgentOs.create({ user: { uid: 0, gid: 0 }, permissions: { fs: 'allow',
childProcess: 'allow', process: 'allow', env: 'deny', hostFunction: 'allow', network: 'deny' },
mounts: [{ path: '/workspace', plugin: { id: 'chunked_s3', config: { bucket,
prefix: 'shell/', region: 'us-east-1', endpoint, metadataPath,
credentials: { accessKeyId: 'test', secretAccessKey: 'test' } } } }] });
const children = (await fs.readFile(`/proc/${process.pid}/task/${process.pid}/children`, 'utf8')).trim().split(/\s+/).filter(Boolean);
console.log(JSON.stringify({ event: 'native_children', children: await Promise.all(children.map(async pid => {
const executable = await fs.realpath(`/proc/${pid}/exe`);
return { pid, executable, sha256: createHash('sha256').update(await fs.readFile(executable)).digest('hex') };
})) }));
for (const [label, command, expectedOutcome, expectedOutput] of [
['printf-read', "printf 'BEGIN\\n'; printf 'owned\\n' > /workspace/owned.txt; cat /workspace/owned.txt; printf 'END\\n'", 'succeeded', 'BEGIN\nowned\nEND\n'],
['awk-begin-open-stdin', 'awk \'BEGIN { print "AWK" }\'', 'timed_out', 'AWK\n'],
['awk-begin-explicit-exit', 'awk \'BEGIN { print "AWK"; exit }\'', 'succeeded', 'AWK\n'],
['awk-begin-null-input', 'awk \'BEGIN { print "AWK" }\' < /dev/null', 'succeeded', 'AWK\n'],
]) {
const start = Date.now(); console.log(JSON.stringify({ event: 'execution_begin', label, command, start }));
const result = await vm.process.exec(command, { cwd: '/workspace', timeoutMs: 5000, output: { capture: 'all' } });
console.log(JSON.stringify({ event: 'execution_end', label, command, elapsedMs: Date.now() - start, result }));
assert.equal(result.outcome, expectedOutcome); assert.equal(result.stdout, expectedOutput);
assert.equal(result.exitCode, expectedOutcome === 'succeeded' ? 0 : 137); assert.equal(result.stderr, '');
}
assert.equal(Buffer.from(await vm.filesystem.readFile('/workspace/owned.txt')).toString(), 'owned\n');
console.log(JSON.stringify({ event: 'diagnosis', reproducedBeginOnlyInputWait: true, minimalShellPassed: true }));
} finally {
await vm?.dispose();
if (bucketCreated) {
for (const object of (await send(new S3.ListObjectsV2Command({ Bucket: bucket }))).Contents ?? [])
await send(new S3.DeleteObjectCommand({ Bucket: bucket, Key: object.Key }));
await send(new S3.DeleteBucketCommand({ Bucket: bucket }));
await assert.rejects(() => send(new S3.HeadBucketCommand({ Bucket: bucket })), error => error.$metadata?.httpStatusCode === 404);
}
await fs.rm(directory, { recursive: true, force: true });
await assert.rejects(() => fs.stat(directory), error => error.code === 'ENOENT');
s3.destroy();
console.log(JSON.stringify({ event: 'cleanup', bucketAbsentVerified: bucketCreated, metadataDirectoryAbsentVerified: true }));
}
process.exit(0);
Source evidence and suspected cause
Two input-handling details interact here; this report does not claim they are two independently reproduced defects.
- In native language_execution.rs around 1809, the attached execution path writes supplied stdin bytes but does not close stdin. The SDK serializes
stdin: ''as an empty buffer, whereas omitted stdin is null. The older noninteractive kernel exec path explicitly closes stdin after optional bytes. - The gawk wrapper supplies an input reader. That alone does not prove it is read. The actual awk-rs 0.1.0 crate, SHA256
ed6041a9be45690d0003e8d2ea488cb0a578104e591944398d96d9b7d9a0fbf9, matches the lockfile. Itssrc/interpreter/mod.rs:238-276executes BEGIN and then enters input processing unlessshould_exitwas set;:307-308callsread_line. - Native sidecar SHA256 in both runs:
1f28dd8d6bc95dfed8926c36432eac8e5f86a0a578b6a9afef96f380a0b6ccfd. Installed gawk.aospkg SHA256:29577ba7506191790426118d5e64a3f09c1cc4be5dab5ca938ffd7b70ba1229d; embedded awk WASM:6fa4fc4d8cc515a5febf4c6a1bee68455494a2e7487bc4e8c854b056b2f98992.
Related earlier stdin repairs #2043 and #2046 shipped before 0.4.6; the reproduction above uses the newer attached shell_execution path. No matching BEGIN-only issue or later release/fix was found in the current search.
We have retained process.exec rather than replacing its structured result/capture/deadline contract with a custom spawn adapter. The requested behavior is simply that this finite command returns success without a shell rewrite, explicit-exit workaround, or an application-owned process manager.
- Lenguaje dominante
- Rust
- Estrellas
- 4.8k
- Forks
- 270
- Merge medio
- 13 h 37 min
- PR fusionados (30 d)
- 48
Preparar el entorno
Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de rivet-dev/agentos
-
Dificultad 4/5 3-5 días Aptitud para principiantes 45/100
rivet-dev/agentos#2087 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
-
Host-function command output doesn't reach a shell pipe, redirection or command substitutionAbierto
Dificultad 4/5 3-5 días Aptitud para principiantes 55/100
Los mantenedores suelen responder en 1 día
-
Dificultad 4/5 3-5 días Aptitud para principiantes 48/100
Los mantenedores suelen responder en 1 día
-
Dificultad 3/5 Medio día Aptitud para principiantes 32/100
rivet-dev/agentos#2020 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Inline JavaScript resolves dynamic import() from / instead of the working directoryPosiblemente ocupada @mittal-parth la tomó hace 45 días. Abierto
Dificultad 3/5 1-2 días Aptitud para principiantes 68/100
Los mantenedores suelen responder en 1 día
Todos los issues de rivet-dev/agentos
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 77/100
near/near-sdk-rs#1676 ·
Los mantenedores suelen responder en 2 días
-
auto:producer auto:security
Dificultad 2/5 1-3 horas Aptitud para principiantes 66/100
fritzprix/libr-agent#2595 ·
Los mantenedores suelen responder en 1 día
-
area:fs type:fix
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
Tripstack-Corp/spyc#1002 ·
Los mantenedores suelen responder en 1 día
-
github_actions security
Dificultad 2/5 1-3 horas Aptitud para principiantes 85/100
dekobon/big-code-analysis#1815 ·
Los mantenedores suelen responder en 1 día
-
[Detail Bug] Request logs can be forged via control characters in fetch object keys (log injection)Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
s2-streamstore/cachey#211 ·
Los mantenedores suelen responder en 1 día