# tecs.io.Process Streaming child processes. `new` starts the child immediately and returns its live standard-stream endpoints. Piped stdin is a [`Writer`](/modules/io/Process/#tecs.io.Process.Writer); piped stdout and stderr are [`Reader`](/modules/io/Process/#tecs.io.Process.Reader) values. The process owns all three endpoints. Closing one endpoint closes only that pipe; closing the process closes every endpoint, terminates a live child, and reaps it. Buffers and byte views passed to an endpoint remain caller-owned. `Process:communicate` is the complete-exchange form for tools and build steps. It feeds stdin while draining stdout and stderr concurrently, so neither output pipe can fill and deadlock the child: ```teal tecs.scoped(function(scope: tecs.Scope) local child = scope:own(tecs.io.Process.new({ args = {"git", "status", "--porcelain"}, timeoutMs = 2000, })) local result, communicateReason = child:communicate() if result == nil then error(communicateReason) end if result:succeeded() then print(result.output) else io.stderr:write(result.errorOutput) end end) ``` Interactive children use the same reader and writer vocabulary as files, buffers, and transforms. Closing stdin sends EOF: ```teal tecs.scoped(function(scope: tecs.Scope) local child = scope:own(tecs.io.Process.new({ args = {"/bin/cat"} })) local written, writeReason = child.stdin:write("one request\n") if written == nil then error(writeReason) end child.stdin:close() local reply, readReason = child.stdout:read(4096) if reply == nil then error(readReason) end print(reply) end) ``` Frame-driven code uses `readAvailable`, `readAvailableInto`, and the matching writer methods. Nil without a reason means an open pipe has no bytes or capacity right now; an empty string or zero means output EOF. These methods make backpressure visible instead of growing an implicit queue. Buffer and byte-view variants avoid constructing Lua strings. Use `"inherit"` for a CLI that should share the terminal, `"null"` to discard a stream, and `stderr = "stdout"` for one ordered transcript. A nonzero child exit still produces a ready [`Exit`](/modules/io/Process/#tecs.io.Process.Exit); `succeeded` performs the separate exit-code check. Spawn failures return nil and a reason. Deadlines and explicit kills set the exit's `killed` fields. ## Module contents ### Constructors | Constructor | Description | | --- | --- | | [`new`](/modules/io/Process/#tecs.io.Process.new) | Starts a caller-owned child process with streaming standard I/O. | ### Types | Type | Kind | Description | | --- | --- | --- | | [`CommunicateOptions`](/modules/io/Process/#tecs.io.Process.CommunicateOptions) | record | CommunicateOptions controls a complete duplex exchange. | | [`ErrorMode`](/modules/io/Process/#tecs.io.Process.ErrorMode) | enum | ErrorMode selects where a child writes standard error. | | [`Exit`](/modules/io/Process/#tecs.io.Process.Exit) | record | Exit describes how a child stopped. | | [`InputMode`](/modules/io/Process/#tecs.io.Process.InputMode) | enum | InputMode selects where a child reads standard input. | | [`Options`](/modules/io/Process/#tecs.io.Process.Options) | record | Options configures a child and its standard streams. | | [`OutputMode`](/modules/io/Process/#tecs.io.Process.OutputMode) | enum | OutputMode selects where a child writes standard output. | | [`Reader`](/modules/io/Process/#tecs.io.Process.Reader) | interface | Reader reads one live child-process output pipe. | | [`Result`](/modules/io/Process/#tecs.io.Process.Result) | record | Result contains a completed duplex exchange. | | [`Writer`](/modules/io/Process/#tecs.io.Process.Writer) | interface | Writer writes one live child-process input pipe. | ### Functions | Function | Kind | Description | | --- | --- | --- | | [`communicate`](/modules/io/Process/#tecs.io.Process.communicate) | Instance | Feeds stdin and captures stdout and stderr without pipe deadlock. | | [`isRunning`](/modules/io/Process/#tecs.io.Process.isRunning) | Instance | Returns whether the child has not stopped yet. | | [`kill`](/modules/io/Process/#tecs.io.Process.kill) | Instance | Requests that the child terminate. | ### Values | Value | Type | Description | | --- | --- | --- | | [`finished`](/modules/io/Process/#tecs.io.Process.finished) | [`Future`](/modules/Future/) | Read-only. Settles ready with the exit description after the child stops. | | [`pid`](/modules/io/Process/#tecs.io.Process.pid) | `integer` | Read-only. Reports the operating-system process identifier. | | [`stderr`](/modules/io/Process/#tecs.io.Process.stderr) | [`Reader`](/modules/io/Process/#tecs.io.Process.Reader) | Read-only. Provides standard error when its mode is "pipe", or nil when error output is inherited, discarded, or... | | [`stdin`](/modules/io/Process/#tecs.io.Process.stdin) | [`Writer`](/modules/io/Process/#tecs.io.Process.Writer) | Read-only. Provides standard input when its mode is "pipe", or nil when input is inherited or discarded. | | [`stdout`](/modules/io/Process/#tecs.io.Process.stdout) | [`Reader`](/modules/io/Process/#tecs.io.Process.Reader) | Read-only. Provides standard output when its mode is "pipe", or nil when output is inherited or discarded. | ## Constructors ### tecs.io.Process.new Static Starts a caller-owned child process with streaming standard I/O. Piped endpoints are nonblocking at their `readAvailable` and `writeAvailable` boundaries. The process owns them, while buffers and byte views passed to endpoint methods remain caller-owned. ```teal function tecs.io.Process.new(options: Options): Process, string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `options` | [`Options`](/modules/io/Process/#tecs.io.Process.Options) | The caller supplies the program, arguments, environment, working directory, standard-stream modes, and optional deadline. | #### Returns | Type | Description | | --- | --- | | [`Process`](/modules/io/Process/) | Returns a caller-owned process when the platform starts it. | | `string` | Returns the platform reason when the first return is nil. | #### Examples ```teal tecs.scoped(function(scope: tecs.Scope) local child = scope:own(tecs.io.Process.new({ args = {"git", "status", "--porcelain"}, cwd = tecs.io.Path.new("workspace"), })) local result, communicateReason = child:communicate() assert(result, communicateReason) end) ``` ```teal tecs.scoped(function(scope: tecs.Scope) local child = scope:own(tecs.io.Process.new({ args = {"git", "rev-parse", "HEAD"}, timeoutMs = 2000, })) local result, communicateReason = child:communicate() assert(result, communicateReason) assert(result:succeeded()) print(result.output) end) ``` ## Types ### tecs.io.Process.CommunicateOptions record `CommunicateOptions` controls a complete duplex exchange. ```teal record tecs.io.Process.CommunicateOptions input: string | ByteView maxOutputBytes: integer end ``` #### tecs.io.Process.CommunicateOptions.input field Caller-writable. Supplies complete standard-input bytes as a string, open [`Buffer`](/modules/io/#tecs.io.Buffer), or open [`ByteView`](/modules/io/#tecs.io.ByteView). Omitted input sends EOF immediately. ```teal tecs.io.Process.CommunicateOptions.input: string | ByteView ``` #### tecs.io.Process.CommunicateOptions.maxOutputBytes field Caller-writable. Limits stdout and stderr together. Defaults to 268,435,456 bytes. Exceeding it forcibly terminates the child and returns a failure. ```teal tecs.io.Process.CommunicateOptions.maxOutputBytes: integer ``` ### tecs.io.Process.ErrorMode enum `ErrorMode` selects where a child writes standard error. ```teal enum tecs.io.Process.ErrorMode "inherit" "null" "pipe" "stdout" end ``` ### tecs.io.Process.Exit record `Exit` describes how a child stopped. ```teal record tecs.io.Process.Exit exitCode: integer killed: boolean timedOut: boolean succeeded: function(self): boolean end ``` #### tecs.io.Process.Exit.exitCode field Read-only. Reports the platform exit code, or the platform's best answer after termination. ```teal tecs.io.Process.Exit.exitCode: integer ``` #### tecs.io.Process.Exit.killed field Read-only. Reports whether Tecs requested termination. ```teal tecs.io.Process.Exit.killed: boolean ``` #### tecs.io.Process.Exit.timedOut field Read-only. Reports whether the configured deadline requested that termination. ```teal tecs.io.Process.Exit.timedOut: boolean ``` #### tecs.io.Process.Exit:succeeded Instance Returns whether the child exited normally with code zero. ```teal function tecs.io.Process.Exit.succeeded(self): boolean ``` ##### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `ProcessExit` | The completed exit to inspect. | ##### Returns | Type | Description | | --- | --- | | `boolean` | Returns true only for a normal zero exit. | ### tecs.io.Process.InputMode enum `InputMode` selects where a child reads standard input. ```teal enum tecs.io.Process.InputMode "inherit" "null" "pipe" end ``` ### tecs.io.Process.Options record `Options` configures a child and its standard streams. ```teal record tecs.io.Process.Options args: {string} cwd: string | Path env: {string: string} clearEnv: boolean stdin: ProcessInputMode stdout: ProcessOutputMode stderr: ProcessErrorMode timeoutMs: integer end ``` #### tecs.io.Process.Options.args field Caller-writable. Supplies the program in `args[1]` followed by its arguments. A program without a separator resolves through `PATH`. ```teal tecs.io.Process.Options.args: {string} ``` #### tecs.io.Process.Options.cwd field Caller-writable. Selects the child's working directory as a string or [`Path`](/modules/io/Path/), or omits it to inherit the current directory. ```teal tecs.io.Process.Options.cwd: string | Path ``` #### tecs.io.Process.Options.env field Caller-writable. Overlays environment variables on the inherited environment, or supplies the complete environment with `clearEnv`. ```teal tecs.io.Process.Options.env: {string: string} ``` #### tecs.io.Process.Options.clearEnv field Caller-writable. Starts with an empty environment when true. Defaults to false. ```teal tecs.io.Process.Options.clearEnv: boolean ``` #### tecs.io.Process.Options.stdin field Caller-writable. Selects `"pipe"`, `"inherit"`, or `"null"` for standard input. Defaults to `"pipe"`. ```teal tecs.io.Process.Options.stdin: ProcessInputMode ``` #### tecs.io.Process.Options.stdout field Caller-writable. Selects `"pipe"`, `"inherit"`, or `"null"` for standard output. Defaults to `"pipe"`. ```teal tecs.io.Process.Options.stdout: ProcessOutputMode ``` #### tecs.io.Process.Options.stderr field Caller-writable. Selects `"pipe"`, `"inherit"`, `"null"`, or `"stdout"` for standard error. Defaults to `"pipe"`. ```teal tecs.io.Process.Options.stderr: ProcessErrorMode ``` #### tecs.io.Process.Options.timeoutMs field Caller-writable. Forcibly terminates the child after this many milliseconds, or omits the deadline. The deadline begins when the process is created. ```teal tecs.io.Process.Options.timeoutMs: integer ``` ### tecs.io.Process.OutputMode enum `OutputMode` selects where a child writes standard output. ```teal enum tecs.io.Process.OutputMode "inherit" "null" "pipe" end ``` ### tecs.io.Process.Reader interface `Reader` reads one live child-process output pipe. ```teal interface tecs.io.Process.Reader is Reader isClosed: function(self): boolean isEOF: function(self): boolean readAvailable: function(self, maxBytes: integer): string, string readAvailableInto: function( self, destination: Buffer, offset: integer, maxBytes: integer ): integer, string end ``` #### Interfaces | Interface | | --- | | [`Reader`](/modules/io/#tecs.io.Reader) | #### tecs.io.Process.Reader:isClosed Instance Returns whether this endpoint has been closed. ```teal function tecs.io.Process.Reader.isClosed(self): boolean ``` ##### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `ProcessReader` | The process reader to inspect. | ##### Returns | Type | Description | | --- | --- | | `boolean` | Returns true after `close`. | #### tecs.io.Process.Reader:isEOF Instance Returns whether the child closed its end and every byte was consumed. ```teal function tecs.io.Process.Reader.isEOF(self): boolean ``` ##### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `ProcessReader` | The process reader to inspect. | ##### Returns | Type | Description | | --- | --- | | `boolean` | Returns true after end of file. | #### tecs.io.Process.Reader:readAvailable Instance Reads immediately available bytes without waiting. Nil without a reason means the pipe remains open but has no bytes ready. An empty string means end of file. ```teal function tecs.io.Process.Reader.readAvailable( self, maxBytes: integer ): string, string ``` ##### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `ProcessReader` | The process reader whose pipe advances. | | `maxBytes` | `integer` | The caller supplies 1 through 65,536 bytes or omits it for 16,384. | ##### Returns | Type | Description | | --- | --- | | `string` | Returns available bytes, nil when none are ready, or an empty string at end of file. | | `string` | Returns a reason only when reading fails. | #### Examples ```teal tecs.scoped(function(scope: tecs.Scope) local child = scope:own(tecs.io.Process.new({ args = {"/bin/sh", "-c", "sleep 1; printf ready"}, })) local chunk , readReason = child.stdout:readAvailable() assert(chunk == nil) assert(readReason == nil) end) ``` #### tecs.io.Process.Reader:readAvailableInto Instance Reads immediately available bytes into reusable storage. Nil without a reason means no bytes are ready. Zero means end of file. ```teal function tecs.io.Process.Reader.readAvailableInto( self, destination: Buffer, offset: integer, maxBytes: integer ): integer, string ``` ##### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `ProcessReader` | The process reader whose pipe advances. | | `destination` | [`Buffer`](/modules/io/#tecs.io.Buffer) | The caller supplies an open destination buffer and retains ownership. | | `offset` | `integer` | The caller supplies a non-negative destination offset or omits it for zero. | | `maxBytes` | `integer` | The caller supplies 1 through 65,536 bytes or omits it for 16,384. | ##### Returns | Type | Description | | --- | --- | | `integer` | Returns bytes read, nil when none are ready, or zero at end of file. | | `string` | Returns a reason only when reading fails. | ### tecs.io.Process.Result record `Result` contains a completed duplex exchange. ```teal record tecs.io.Process.Result exit: ProcessExit output: string errorOutput: string succeeded: function(self): boolean end ``` #### tecs.io.Process.Result.exit field Read-only. Contains the child's exit description. ```teal tecs.io.Process.Result.exit: ProcessExit ``` #### tecs.io.Process.Result.output field Read-only. Contains every captured standard-output byte. It is empty when stdout was inherited or discarded. ```teal tecs.io.Process.Result.output: string ``` #### tecs.io.Process.Result.errorOutput field Read-only. Contains every captured standard-error byte. It is empty when stderr was inherited, discarded, or merged into stdout. ```teal tecs.io.Process.Result.errorOutput: string ``` #### tecs.io.Process.Result:succeeded Instance Returns whether the child exited normally with code zero. ```teal function tecs.io.Process.Result.succeeded(self): boolean ``` ##### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `ProcessResult` | The completed exchange to inspect. | ##### Returns | Type | Description | | --- | --- | | `boolean` | Returns true only for a normal zero exit. | ### tecs.io.Process.Writer interface `Writer` writes one live child-process input pipe. ```teal interface tecs.io.Process.Writer is Writer isClosed: function(self): boolean writeAvailable: function( self, bytes: string, offset: integer, count: integer ): integer, string writeAvailableFrom: function( self, source: Buffer, offset: integer, count: integer ): integer, string writeAvailableView: function( self, source: ByteView, offset: integer, count: integer ): integer, string end ``` #### Interfaces | Interface | | --- | | [`Writer`](/modules/io/#tecs.io.Writer) | #### tecs.io.Process.Writer:isClosed Instance Returns whether this endpoint has sent EOF or been closed. ```teal function tecs.io.Process.Writer.isClosed(self): boolean ``` ##### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `ProcessWriter` | The process writer to inspect. | ##### Returns | Type | Description | | --- | --- | | `boolean` | Returns true after `close`. | #### tecs.io.Process.Writer:writeAvailable Instance Writes as many immediately acceptable string bytes as possible. Nil without a reason means the pipe has no capacity yet. ```teal function tecs.io.Process.Writer.writeAvailable( self, bytes: string, offset: integer, count: integer ): integer, string ``` ##### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `ProcessWriter` | The process writer whose pipe advances. | | `bytes` | `string` | The caller supplies binary bytes including embedded NULs. | | `offset` | `integer` | The caller supplies a zero-based string offset or omits it for zero. | | `count` | `integer` | The caller supplies a byte count or omits it for the remainder. | ##### Returns | Type | Description | | --- | --- | | `integer` | Returns bytes accepted, or nil when none fit immediately. | | `string` | Returns a reason only when writing fails. | #### tecs.io.Process.Writer:writeAvailableFrom Instance Writes an immediately acceptable buffer prefix without a Lua string. ```teal function tecs.io.Process.Writer.writeAvailableFrom( self, source: Buffer, offset: integer, count: integer ): integer, string ``` ##### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `ProcessWriter` | The process writer whose pipe advances. | | `source` | [`Buffer`](/modules/io/#tecs.io.Buffer) | The caller supplies an open source buffer and retains ownership. | | `offset` | `integer` | The caller supplies a zero-based source offset or omits it for zero. | | `count` | `integer` | The caller supplies a byte count or omits it for the remainder. | ##### Returns | Type | Description | | --- | --- | | `integer` | Returns bytes accepted, or nil when none fit immediately. | | `string` | Returns a reason only when writing fails. | #### tecs.io.Process.Writer:writeAvailableView Instance Writes an immediately acceptable immutable-view prefix. ```teal function tecs.io.Process.Writer.writeAvailableView( self, source: ByteView, offset: integer, count: integer ): integer, string ``` ##### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `ProcessWriter` | The process writer whose pipe advances. | | `source` | [`ByteView`](/modules/io/#tecs.io.ByteView) | The caller supplies an open source view and retains ownership. | | `offset` | `integer` | The caller supplies a zero-based source offset or omits it for zero. | | `count` | `integer` | The caller supplies a byte count or omits it for the remainder. | ##### Returns | Type | Description | | --- | --- | | `integer` | Returns bytes accepted, or nil when none fit immediately. | | `string` | Returns a reason only when writing fails. | ## Functions ### tecs.io.Process:communicate Instance Feeds stdin and captures stdout and stderr without pipe deadlock. The call blocks the current Lua thread, drains both output pipes while feeding input, closes stdin after the final byte, and returns only after the child exits and both captured pipes reach EOF. Run it in a worker when blocking is inappropriate. ```teal function tecs.io.Process.communicate( self, options: CommunicateOptions ): Result, string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `Process` | The open process whose piped endpoints are consumed. | | `options` | [`CommunicateOptions`](/modules/io/Process/#tecs.io.Process.CommunicateOptions) | The caller supplies complete input and an output limit, or omits both. | #### Returns | Type | Description | | --- | --- | | [`Result`](/modules/io/Process/#tecs.io.Process.Result) | Returns the complete exchange result. | | `string` | Returns a reason when communication fails or exceeds its output limit. | #### Examples ```teal tecs.scoped(function(scope: tecs.Scope) local child = scope:own(tecs.io.Process.new({ args = {"/bin/cat"} })) local result, communicateReason = child:communicate({ input = "request bytes\n", maxOutputBytes = 1024 * 1024, }) assert(result, communicateReason) assert(result.output == "request bytes\n") end) ``` ### tecs.io.Process:isRunning Instance Returns whether the child has not stopped yet. ```teal function tecs.io.Process.isRunning(self): boolean ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `Process` | The process to inspect. | #### Returns | Type | Description | | --- | --- | | `boolean` | Returns true while the child remains live. | ### tecs.io.Process:kill Instance Requests that the child terminate. ```teal function tecs.io.Process.kill(self, force: boolean): boolean, string ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `Process` | The process to terminate. | | `force` | `boolean` | The caller requests an unhandleable termination when true or a graceful request when false or omitted. | #### Returns | Type | Description | | --- | --- | | `boolean` | Returns whether the request was delivered or the child had already stopped. | | `string` | Returns the platform reason when the first return is false. | ## Values ### tecs.io.Process.finished variable Read-only. Settles ready with the exit description after the child stops. Canceling this future does not terminate the separately owned process. ```teal tecs.io.Process.finished: Future ``` ### tecs.io.Process.pid variable Read-only. Reports the operating-system process identifier. ```teal tecs.io.Process.pid: integer ``` ### tecs.io.Process.stderr variable Read-only. Provides standard error when its mode is `"pipe"`, or nil when error output is inherited, discarded, or merged into stdout. The process owns it. ```teal tecs.io.Process.stderr: Reader ``` ### tecs.io.Process.stdin variable Read-only. Provides standard input when its mode is `"pipe"`, or nil when input is inherited or discarded. The process owns it. ```teal tecs.io.Process.stdin: Writer ``` ### tecs.io.Process.stdout variable Read-only. Provides standard output when its mode is `"pipe"`, or nil when output is inherited or discarded. The process owns it. ```teal tecs.io.Process.stdout: Reader ```