From 821cac7f6960311a78555ff862d6cfacacb69bc4 Mon Sep 17 00:00:00 2001 From: Ansel-2428 Date: Sun, 18 Oct 2026 13:16:50 -0400 Subject: [PATCH 1/4] fix(code-execution): the advice after a signal fits the signal: a crash's points at native code, and only SIGKILL keeps the out-of-memory example When a signal ended the Python interpreter under a script, the result's advice gave "by using too much memory" as its example of the script ending the process, whatever the signal. A crash's signal, one of the five faulthandler writes a traceback for (SIGSEGV, SIGABRT, SIGBUS, SIGFPE and SIGILL), now gets "check the traceback above for native code that crashed, such as an extension module the script uses". SIGKILL, which the out-of-memory killer sends, keeps the memory example, and any other signal gets os.kill() as its example. Co-Authored-By: Claude Opus 5.5 --- .../fix-code-execution-signal-advice.fixed.md | 1 + src/code-execution/py-runner.ts | 10 +++++- test/code-execution.test.ts | 33 ++++++++++++++++--- 3 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 changelog.d/fix-code-execution-signal-advice.fixed.md diff --git a/changelog.d/fix-code-execution-signal-advice.fixed.md b/changelog.d/fix-code-execution-signal-advice.fixed.md new file mode 100644 index 000000000..e68b29af3 --- /dev/null +++ b/changelog.d/fix-code-execution-signal-advice.fixed.md @@ -0,0 +1 @@ +- When a crash's signal (SIGSEGV, SIGABRT, SIGBUS, SIGFPE or SIGILL) ends `code_execution`'s Python interpreter while a script runs, the result now says to check the traceback above it for native code that crashed, such as an extension module the script uses. It used to say to check whether the script uses too much memory, which now comes only after SIGKILL, the signal the out-of-memory killer sends. Any other signal gets `os.kill()` as its example. diff --git a/src/code-execution/py-runner.ts b/src/code-execution/py-runner.ts index 0fe6c2f1b..471076402 100644 --- a/src/code-execution/py-runner.ts +++ b/src/code-execution/py-runner.ts @@ -581,6 +581,9 @@ function stoppedReading(background: boolean): string { + "shuts down or replaces file descriptor 3, where the host's messages arrive; if not, ask whoever runs this host."; } +/** The signals faulthandler writes a traceback for (runtime-py.ts), which a crash ends a process with. */ +const CRASH_SIGNALS: ReadonlySet = new Set(['SIGSEGV', 'SIGABRT', 'SIGBUS', 'SIGFPE', 'SIGILL']); + /** * Why a script stopped when its interpreter exited under it, and what running it * again does; or, when the script's result came before the exit (ended), that @@ -593,9 +596,14 @@ function interpreterExit(exitCode: number | null, signal: NodeJS.Signals | null, // copy of fd 2 and fd 2 itself, or shut down the socket under them, and it's read from what the result // keeps, which past a stream's cap or a journal's tail lacks it. const taken = exitCode === RUNTIME_FATAL_EXIT ? fdTakenFromRuntime(said) : null; + // A crash's signal comes with the traceback faulthandler writes, and more often from native code than + // from the script; SIGKILL is the out-of-memory killer's; any other signal, the script can send itself. const check = taken ? `check whether the script closes, shuts down or replaces ${taken}, as the runtime's line above says` - : `check whether the script itself ends the process, for example ${signal ? 'by using too much memory' : 'with os._exit()'}`; + : signal && CRASH_SIGNALS.has(signal) + ? 'check the traceback above for native code that crashed, such as an extension module the script uses' + : `check whether the script itself ends the process, for example ${ + signal === 'SIGKILL' ? 'by using too much memory' : signal ? 'with os.kill()' : 'with os._exit()'}`; const gone = background ? '' : ', and variables from earlier scripts are gone'; if (ended) { return `script ended, but the Python interpreter ${how} before all of its output reached the host, so some of it may be missing${gone}. ` diff --git a/test/code-execution.test.ts b/test/code-execution.test.ts index 23b1cf6ca..1dd9ea75f 100644 --- a/test/code-execution.test.ts +++ b/test/code-execution.test.ts @@ -66,12 +66,16 @@ const alive = (pid: number): boolean => { }; /** What a script's result says when its interpreter exits under it (py-runner's - * interpreterExit), for `how` such as "exited with code 3" or "was ended by SIGKILL". */ -const exitedUnder = (how: string, background = false): string => + * interpreterExit), for `how` such as "exited with code 3" or "was ended by SIGKILL"; + * after any other signal, `check` gives the advice's first step. */ +const exitedUnder = (how: string, background = false, check?: string): string => `script stopped: the Python interpreter ${how} while it ran, so the rest of the script didn't run` + `${background ? '' : ', and variables from earlier scripts are gone'}. Tool calls it had already made keep their effects, ` - + 'so check them before you run it again in a new interpreter. If it stops the same way, check whether the script itself ' - + `ends the process, for example ${how.startsWith('was ended by') ? 'by using too much memory' : 'with os._exit()'}; if not, ask whoever runs this host.`; + + `so check them before you run it again in a new interpreter. If it stops the same way, ${check ?? 'check whether the script itself ' + + `ends the process, for example ${how === 'was ended by SIGKILL' ? 'by using too much memory' : 'with os._exit()'}`}; if not, ask whoever runs this host.`; + +/** The advice's first step after a crash's signal, where faulthandler's traceback is above it. */ +const NATIVE_CRASH = 'check the traceback above for native code that crashed, such as an extension module the script uses'; /** * What a script's result says when its interpreter exits after the script's own @@ -1458,6 +1462,27 @@ describe("PyRunner: a script's output, read by the host (real python3)", { skip: } }); + it("a signal's advice fits it: native code after a crash's signal, the script's own os.kill() after another", async () => { + const cases: [string, string, string][] = [ + ['a segfault', 'import ctypes\nctypes.string_at(0)', exitedUnder('was ended by SIGSEGV', false, NATIVE_CRASH)], + ['a fatal error', 'import ctypes\nctypes.pythonapi.Py_FatalError(b"probe reason")', exitedUnder('was ended by SIGABRT', false, NATIVE_CRASH)], + ...['SIGBUS', 'SIGFPE', 'SIGILL'].map((sig): [string, string, string] => + [sig, `import os, signal\nos.kill(os.getpid(), signal.${sig})`, exitedUnder(`was ended by ${sig}`, false, NATIVE_CRASH)]), + ['SIGTERM', 'import os, signal\nos.kill(os.getpid(), signal.SIGTERM)', exitedUnder('was ended by SIGTERM', false, + 'check whether the script itself ends the process, for example with os.kill()')], + ]; + for (const [what, code, says] of cases) { + const runner = new PyRunner({ onToolCall: async () => '' }); + try { + const result = await within(runner.exec(code, []), what); + assert.strictEqual(result.aborted, true, what); + assert.ok(result.stderr.endsWith(`\n${says}`) || result.stderr === says, `${what}: ${JSON.stringify(result.stderr.slice(-600))}`); + } finally { + runner.dispose(); + } + } + }); + it('an interpreter that ends under its script leaves in the result all it wrote', async () => { const runner = new PyRunner({ onToolCall: async () => '' }); try { From 03294a3a60b7ef249ae3b261009e761fc8ffe06d Mon Sep 17 00:00:00 2001 From: Ansel-2428 Date: Sun, 18 Oct 2026 14:09:35 -0400 Subject: [PATCH 2/4] fix(code-execution): the crash advice points at the traceback only when the result holds it, and names the call that crashed in native code Answers an internal review of the change before it. - After a crash's signal, interpreterExit reads what the result keeps for faulthandler's first words, "Fatal Python error: ". With them, it says "check the traceback above for the call that crashed in native code, such as an extension module the script uses", since the traceback shows Python frames, the call into native code among them. Without them, as when a script disabled faulthandler, a stream reached its cap or a journal's tail lost them, it says "check whether native code the script calls crashes, such as an extension module it uses". - The comment no longer says a crash's signal more often comes from native code: the traceback shows the call either way, a script's own os.abort() included. Tests: each crash case checks that faulthandler's traceback is in the result, SIGBUS, SIGFPE and SIGILL included; a segfault after faulthandler.disable() gets the advice that names native code alone; and the test helper's example for a signal other than SIGKILL is os.kill(), as the code's is. Co-Authored-By: Claude Opus 5.5 --- .../fix-code-execution-signal-advice.fixed.md | 2 +- src/code-execution/py-runner.ts | 13 ++++-- test/code-execution.test.ts | 40 ++++++++++++------- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/changelog.d/fix-code-execution-signal-advice.fixed.md b/changelog.d/fix-code-execution-signal-advice.fixed.md index e68b29af3..779fa9ac0 100644 --- a/changelog.d/fix-code-execution-signal-advice.fixed.md +++ b/changelog.d/fix-code-execution-signal-advice.fixed.md @@ -1 +1 @@ -- When a crash's signal (SIGSEGV, SIGABRT, SIGBUS, SIGFPE or SIGILL) ends `code_execution`'s Python interpreter while a script runs, the result now says to check the traceback above it for native code that crashed, such as an extension module the script uses. It used to say to check whether the script uses too much memory, which now comes only after SIGKILL, the signal the out-of-memory killer sends. Any other signal gets `os.kill()` as its example. +- When a crash's signal (SIGSEGV, SIGABRT, SIGBUS, SIGFPE or SIGILL) ends `code_execution`'s Python interpreter while a script runs, the result now says to check the traceback above it for the call that crashed in native code, such as an extension module the script uses, or, when no traceback reached the result, to check whether native code the script calls crashes. It used to say to check whether the script uses too much memory, which now comes only after SIGKILL, the signal the out-of-memory killer sends. Any other signal gets `os.kill()` as its example. diff --git a/src/code-execution/py-runner.ts b/src/code-execution/py-runner.ts index 471076402..174540c9c 100644 --- a/src/code-execution/py-runner.ts +++ b/src/code-execution/py-runner.ts @@ -584,6 +584,9 @@ function stoppedReading(background: boolean): string { /** The signals faulthandler writes a traceback for (runtime-py.ts), which a crash ends a process with. */ const CRASH_SIGNALS: ReadonlySet = new Set(['SIGSEGV', 'SIGABRT', 'SIGBUS', 'SIGFPE', 'SIGILL']); +/** How faulthandler's traceback for a crash's signal begins. */ +const CRASH_TRACEBACK = 'Fatal Python error: '; + /** * Why a script stopped when its interpreter exited under it, and what running it * again does; or, when the script's result came before the exit (ended), that @@ -596,12 +599,16 @@ function interpreterExit(exitCode: number | null, signal: NodeJS.Signals | null, // copy of fd 2 and fd 2 itself, or shut down the socket under them, and it's read from what the result // keeps, which past a stream's cap or a journal's tail lacks it. const taken = exitCode === RUNTIME_FATAL_EXIT ? fdTakenFromRuntime(said) : null; - // A crash's signal comes with the traceback faulthandler writes, and more often from native code than - // from the script; SIGKILL is the out-of-memory killer's; any other signal, the script can send itself. + // After a crash's signal, faulthandler's traceback shows the call that crashed, in native code or the + // script's own os.abort(), when it reached what the result keeps: a script can disable faulthandler, + // and a stream's cap or a journal's tail can lose it. SIGKILL is the out-of-memory killer's; any + // other signal, the script can send itself. const check = taken ? `check whether the script closes, shuts down or replaces ${taken}, as the runtime's line above says` : signal && CRASH_SIGNALS.has(signal) - ? 'check the traceback above for native code that crashed, such as an extension module the script uses' + ? said.includes(CRASH_TRACEBACK) + ? 'check the traceback above for the call that crashed in native code, such as an extension module the script uses' + : 'check whether native code the script calls crashes, such as an extension module it uses' : `check whether the script itself ends the process, for example ${ signal === 'SIGKILL' ? 'by using too much memory' : signal ? 'with os.kill()' : 'with os._exit()'}`; const gone = background ? '' : ', and variables from earlier scripts are gone'; diff --git a/test/code-execution.test.ts b/test/code-execution.test.ts index 1dd9ea75f..6aaaf4598 100644 --- a/test/code-execution.test.ts +++ b/test/code-execution.test.ts @@ -66,16 +66,19 @@ const alive = (pid: number): boolean => { }; /** What a script's result says when its interpreter exits under it (py-runner's - * interpreterExit), for `how` such as "exited with code 3" or "was ended by SIGKILL"; - * after any other signal, `check` gives the advice's first step. */ + * interpreterExit), for `how` such as "exited with code 3" or "was ended by SIGKILL", + * with `check` for the advice's first step after a crash's signal. */ const exitedUnder = (how: string, background = false, check?: string): string => `script stopped: the Python interpreter ${how} while it ran, so the rest of the script didn't run` + `${background ? '' : ', and variables from earlier scripts are gone'}. Tool calls it had already made keep their effects, ` + `so check them before you run it again in a new interpreter. If it stops the same way, ${check ?? 'check whether the script itself ' - + `ends the process, for example ${how === 'was ended by SIGKILL' ? 'by using too much memory' : 'with os._exit()'}`}; if not, ask whoever runs this host.`; + + `ends the process, for example ${ + how === 'was ended by SIGKILL' ? 'by using too much memory' : how.startsWith('was ended by') ? 'with os.kill()' : 'with os._exit()'}` + }; if not, ask whoever runs this host.`; -/** The advice's first step after a crash's signal, where faulthandler's traceback is above it. */ -const NATIVE_CRASH = 'check the traceback above for native code that crashed, such as an extension module the script uses'; +/** The advice's first step after a crash's signal, with faulthandler's traceback above it, and without. */ +const NATIVE_CRASH = 'check the traceback above for the call that crashed in native code, such as an extension module the script uses'; +const NATIVE_CRASH_UNSEEN = 'check whether native code the script calls crashes, such as an extension module it uses'; /** * What a script's result says when its interpreter exits after the script's own @@ -1462,21 +1465,28 @@ describe("PyRunner: a script's output, read by the host (real python3)", { skip: } }); - it("a signal's advice fits it: native code after a crash's signal, the script's own os.kill() after another", async () => { - const cases: [string, string, string][] = [ - ['a segfault', 'import ctypes\nctypes.string_at(0)', exitedUnder('was ended by SIGSEGV', false, NATIVE_CRASH)], - ['a fatal error', 'import ctypes\nctypes.pythonapi.Py_FatalError(b"probe reason")', exitedUnder('was ended by SIGABRT', false, NATIVE_CRASH)], - ...['SIGBUS', 'SIGFPE', 'SIGILL'].map((sig): [string, string, string] => - [sig, `import os, signal\nos.kill(os.getpid(), signal.${sig})`, exitedUnder(`was ended by ${sig}`, false, NATIVE_CRASH)]), - ['SIGTERM', 'import os, signal\nos.kill(os.getpid(), signal.SIGTERM)', exitedUnder('was ended by SIGTERM', false, - 'check whether the script itself ends the process, for example with os.kill()')], + it("a signal's advice fits it: the traceback's call after a crash's signal, native code when no traceback came, the script's own os.kill() after another", async () => { + // Each with whether faulthandler's traceback is in the result, above the advice. + const cases: [string, string, boolean, string][] = [ + ['a segfault', 'import ctypes\nctypes.string_at(0)', true, exitedUnder('was ended by SIGSEGV', false, NATIVE_CRASH)], + ['a fatal error', 'import ctypes\nctypes.pythonapi.Py_FatalError(b"probe reason")', true, exitedUnder('was ended by SIGABRT', false, NATIVE_CRASH)], + ...['SIGBUS', 'SIGFPE', 'SIGILL'].map((sig): [string, string, boolean, string] => + [sig, `import os, signal\nos.kill(os.getpid(), signal.${sig})`, true, exitedUnder(`was ended by ${sig}`, false, NATIVE_CRASH)]), + ['a segfault with faulthandler disabled', 'import ctypes, faulthandler\nfaulthandler.disable()\nctypes.string_at(0)', false, + exitedUnder('was ended by SIGSEGV', false, NATIVE_CRASH_UNSEEN)], + ['SIGTERM', 'import os, signal\nos.kill(os.getpid(), signal.SIGTERM)', false, exitedUnder('was ended by SIGTERM')], ]; - for (const [what, code, says] of cases) { + for (const [what, code, traced, says] of cases) { const runner = new PyRunner({ onToolCall: async () => '' }); try { const result = await within(runner.exec(code, []), what); assert.strictEqual(result.aborted, true, what); - assert.ok(result.stderr.endsWith(`\n${says}`) || result.stderr === says, `${what}: ${JSON.stringify(result.stderr.slice(-600))}`); + if (traced) { + assert.match(result.stderr, /^Fatal Python error: /m, what); + assert.ok(result.stderr.endsWith(`\n${says}`), `${what}: ${JSON.stringify(result.stderr.slice(-600))}`); + } else { + assert.strictEqual(result.stderr, says, what); + } } finally { runner.dispose(); } From 518960a74cee66ce059b8ab28918cb643d6b7913 Mon Sep 17 00:00:00 2001 From: Ansel-2428 Date: Sun, 18 Oct 2026 14:30:33 -0400 Subject: [PATCH 3/4] test(code-execution): a crash after a line the script didn't end still gets the advice that points at its traceback faulthandler doesn't start a line of its own: after output that didn't end one, such as a progress bar's, its first words, "Fatal Python error: ", follow on that line. interpreterExit looks for them anywhere in what the result keeps, and its comment now says why: anchored at a line's start, the match would give such a crash the advice for one whose traceback didn't reach the result. Tests: the signal-advice test gains a segfault after a progress bar's line written to stderr and not ended, and checks where faulthandler's traceback begins in each case. Co-Authored-By: Claude Opus 5.5 --- src/code-execution/py-runner.ts | 4 +++- test/code-execution.test.ts | 26 +++++++++++++++----------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/code-execution/py-runner.ts b/src/code-execution/py-runner.ts index 174540c9c..3f112ec3e 100644 --- a/src/code-execution/py-runner.ts +++ b/src/code-execution/py-runner.ts @@ -601,7 +601,9 @@ function interpreterExit(exitCode: number | null, signal: NodeJS.Signals | null, const taken = exitCode === RUNTIME_FATAL_EXIT ? fdTakenFromRuntime(said) : null; // After a crash's signal, faulthandler's traceback shows the call that crashed, in native code or the // script's own os.abort(), when it reached what the result keeps: a script can disable faulthandler, - // and a stream's cap or a journal's tail can lose it. SIGKILL is the out-of-memory killer's; any + // and a stream's cap or a journal's tail can lose it. Its first words are looked for anywhere, not + // only at a line's start: faulthandler doesn't start a line, so after output that didn't end one, + // such as a progress bar's, they follow on that line. SIGKILL is the out-of-memory killer's; any // other signal, the script can send itself. const check = taken ? `check whether the script closes, shuts down or replaces ${taken}, as the runtime's line above says` diff --git a/test/code-execution.test.ts b/test/code-execution.test.ts index 6aaaf4598..3f9af171e 100644 --- a/test/code-execution.test.ts +++ b/test/code-execution.test.ts @@ -1466,23 +1466,27 @@ describe("PyRunner: a script's output, read by the host (real python3)", { skip: }); it("a signal's advice fits it: the traceback's call after a crash's signal, native code when no traceback came, the script's own os.kill() after another", async () => { - // Each with whether faulthandler's traceback is in the result, above the advice. - const cases: [string, string, boolean, string][] = [ - ['a segfault', 'import ctypes\nctypes.string_at(0)', true, exitedUnder('was ended by SIGSEGV', false, NATIVE_CRASH)], - ['a fatal error', 'import ctypes\nctypes.pythonapi.Py_FatalError(b"probe reason")', true, exitedUnder('was ended by SIGABRT', false, NATIVE_CRASH)], - ...['SIGBUS', 'SIGFPE', 'SIGILL'].map((sig): [string, string, boolean, string] => - [sig, `import os, signal\nos.kill(os.getpid(), signal.${sig})`, true, exitedUnder(`was ended by ${sig}`, false, NATIVE_CRASH)]), - ['a segfault with faulthandler disabled', 'import ctypes, faulthandler\nfaulthandler.disable()\nctypes.string_at(0)', false, + // Each with where faulthandler's traceback begins in the result, above the advice, or null where none came. + const header = /^Fatal Python error: /m; + const cases: [string, string, RegExp | null, string][] = [ + ['a segfault', 'import ctypes\nctypes.string_at(0)', header, exitedUnder('was ended by SIGSEGV', false, NATIVE_CRASH)], + ['a fatal error', 'import ctypes\nctypes.pythonapi.Py_FatalError(b"probe reason")', header, exitedUnder('was ended by SIGABRT', false, NATIVE_CRASH)], + ...['SIGBUS', 'SIGFPE', 'SIGILL'].map((sig): [string, string, RegExp, string] => + [sig, `import os, signal\nos.kill(os.getpid(), signal.${sig})`, header, exitedUnder(`was ended by ${sig}`, false, NATIVE_CRASH)]), + // faulthandler doesn't start a line: after a progress bar's, which doesn't end, its first words follow on it. + ["a segfault after a line the script didn't end", 'import sys, ctypes\nsys.stderr.write("\\r 45%|#### | 45/100")\nsys.stderr.flush()\nctypes.string_at(0)', + /45\/100Fatal Python error: /, exitedUnder('was ended by SIGSEGV', false, NATIVE_CRASH)], + ['a segfault with faulthandler disabled', 'import ctypes, faulthandler\nfaulthandler.disable()\nctypes.string_at(0)', null, exitedUnder('was ended by SIGSEGV', false, NATIVE_CRASH_UNSEEN)], - ['SIGTERM', 'import os, signal\nos.kill(os.getpid(), signal.SIGTERM)', false, exitedUnder('was ended by SIGTERM')], + ['SIGTERM', 'import os, signal\nos.kill(os.getpid(), signal.SIGTERM)', null, exitedUnder('was ended by SIGTERM')], ]; - for (const [what, code, traced, says] of cases) { + for (const [what, code, traceback, says] of cases) { const runner = new PyRunner({ onToolCall: async () => '' }); try { const result = await within(runner.exec(code, []), what); assert.strictEqual(result.aborted, true, what); - if (traced) { - assert.match(result.stderr, /^Fatal Python error: /m, what); + if (traceback) { + assert.match(result.stderr, traceback, what); assert.ok(result.stderr.endsWith(`\n${says}`), `${what}: ${JSON.stringify(result.stderr.slice(-600))}`); } else { assert.strictEqual(result.stderr, says, what); From be32e62c049311daab984b428b7cad6186327f22 Mon Sep 17 00:00:00 2001 From: Ansel-2428 Date: Mon, 19 Oct 2026 17:36:16 -0400 Subject: [PATCH 4/4] test(code-execution): the helper for an exit after a script's result writes the signal's advice as the code does exitedAfterEnd built the advice of an interpreter that exits after its script's result with "by using too much memory" for every signal, the text the code wrote before the advice came to fit the signal. It now takes the same default as exitedUnder, the memory example for SIGKILL alone and os.kill() for any other signal, and the same optional check for a crash's signal, so both helpers model the one function. Its uses all pass an exit code, so no test's expectation changes. Co-Authored-By: Claude Opus 5.5 --- test/code-execution.test.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/code-execution.test.ts b/test/code-execution.test.ts index 3f9af171e..7c91df2cb 100644 --- a/test/code-execution.test.ts +++ b/test/code-execution.test.ts @@ -83,14 +83,16 @@ const NATIVE_CRASH_UNSEEN = 'check whether native code the script calls crashes, /** * What a script's result says when its interpreter exits after the script's own * result came, before all of its output arrived (py-runner's interpreterExit, - * ended), with the advice for a runtime that ended itself over `taken`, if given. + * ended), with the advice for a runtime that ended itself over `taken`, if given, + * and `check` for the advice's first step after a crash's signal, as exitedUnder's. */ -const exitedAfterEnd = (how: string, taken?: string, background = false): string => +const exitedAfterEnd = (how: string, taken?: string, background = false, check?: string): string => `script ended, but the Python interpreter ${how} before all of its output reached the host, so some of it may be missing` + `${background ? '' : ', and variables from earlier scripts are gone'}. If it happens again, ` + (taken ? `check whether the script closes, shuts down or replaces ${taken}, as the runtime's line above says` - : `check whether the script itself ends the process, for example ${how.startsWith('was ended by') ? 'by using too much memory' : 'with os._exit()'}`) + : check ?? `check whether the script itself ends the process, for example ${ + how === 'was ended by SIGKILL' ? 'by using too much memory' : how.startsWith('was ended by') ? 'with os.kill()' : 'with os._exit()'}`) + '; if not, ask whoever runs this host.'; /** The result's text after the runtime ended itself because a script took one of its file descriptors. */