Pseudo Terminal (PTY)
Daytona provides powerful pseudo terminal (PTY) capabilities through the process module in sandboxes. PTY sessions allow you to create interactive terminal sessions that can execute commands, handle user input, and manage terminal operations.
A PTY (Pseudo Terminal) is a virtual terminal interface that allows programs to interact with a shell as if they were connected to a real terminal. PTY sessions in Daytona enable:
- Interactive Development: REPLs, debuggers, and development tools
- Build Processes: Running and monitoring compilation, testing, or deployment
- System Administration: Remote server management and configuration
- User Interfaces: Terminal-based applications requiring user interaction
Create PTY session
Section titled “Create PTY session”Create an interactive terminal session that can execute commands and handle user input.
from daytona.common.pty import PtySize
pty_handle = sandbox.process.create_pty_session( id="my-session", cwd="/workspace", envs={"TERM": "xterm-256color"}, pty_size=PtySize(cols=120, rows=30))// Create a PTY session with custom configurationconst ptyHandle = await sandbox.process.createPty({ id: 'my-interactive-session', cwd: '/workspace', envs: { TERM: 'xterm-256color', LANG: 'en_US.UTF-8' }, cols: 120, rows: 30, onData: (data) => { // Handle terminal output const text = new TextDecoder().decode(data) process.stdout.write(text) },})
// Wait for connection to be establishedawait ptyHandle.waitForConnection()
// Send commands to the terminalawait ptyHandle.sendInput('ls -la\n')await ptyHandle.sendInput('echo "Hello, PTY!"\n')await ptyHandle.sendInput('exit\n')
// Wait for completion and get resultconst result = await ptyHandle.wait()console.log(`PTY session completed with exit code: ${result.exitCode}`)
// Clean upawait ptyHandle.disconnect()pty_size = Daytona::PtySize.new(rows: 30, cols: 120)pty_handle = sandbox.process.create_pty_session( id: 'my-interactive-session', cwd: '/workspace', envs: { 'TERM' => 'xterm-256color' }, pty_size: pty_size)
# Use the PTY sessionpty_handle.send_input("ls -la\n")pty_handle.send_input("echo 'Hello, PTY!'\n")pty_handle.send_input("exit\n")
# Handle outputpty_handle.each { |data| print data }
puts "PTY session completed with exit code: #{pty_handle.exit_code}"// Create a PTY session with custom configurationhandle, err := sandbox.Process.CreatePty(ctx, "my-interactive-session", options.WithCreatePtySize(types.PtySize{Cols: 120, Rows: 30}), options.WithCreatePtyEnv(map[string]string{"TERM": "xterm-256color"}),)if err != nil { log.Fatal(err)}defer handle.Disconnect()
// Wait for connection to be establishedif err := handle.WaitForConnection(ctx); err != nil { log.Fatal(err)}
// Send commands to the terminalhandle.SendInput([]byte("ls -la\n"))handle.SendInput([]byte("echo 'Hello, PTY!'\n"))handle.SendInput([]byte("exit\n"))
// Read output from channelfor data := range handle.DataChan() { fmt.Print(string(data))}
// Wait for completion and get resultresult, err := handle.Wait(ctx)if err != nil { log.Fatal(err)}fmt.Printf("PTY session completed with exit code: %d\n", *result.ExitCode)import io.daytona.sdk.PtyCreateOptions;import io.daytona.sdk.PtyHandle;import io.daytona.sdk.PtyResult;import java.nio.charset.StandardCharsets;
PtyCreateOptions options = new PtyCreateOptions() .setId("my-interactive-session") .setCols(120) .setRows(30) .setOnData(chunk -> System.out.print(new String(chunk, StandardCharsets.UTF_8)));
PtyHandle ptyHandle = sandbox.process.createPty(options);ptyHandle.waitForConnection(30);
ptyHandle.sendInput("ls -la\n");ptyHandle.sendInput("echo \"Hello, PTY!\"\n");ptyHandle.sendInput("exit\n");
PtyResult result = ptyHandle.waitForExit();System.out.println("PTY session completed with exit code: " + result.getExitCode());
ptyHandle.disconnect();curl 'https://proxy.app.daytona.io/toolbox/{sandboxId}/process/pty' \ --request POST \ --header 'Content-Type: application/json' \ --data '{ "id": "my-session", "cwd": "/workspace", "cols": 120, "rows": 30, "envs": { "TERM": "xterm-256color" }, "lazyStart": false}'Connect to PTY session
Section titled “Connect to PTY session”Establish a connection to an existing PTY session.
pty_handle = sandbox.process.connect_pty_session("my-session")// Connect to an existing PTY sessionconst handle = await sandbox.process.connectPty('my-session', { onData: (data) => { // Handle terminal output const text = new TextDecoder().decode(data) process.stdout.write(text) },})
// Wait for connection to be establishedawait handle.waitForConnection()
// Send commands to the existing sessionawait handle.sendInput('pwd\n')await handle.sendInput('ls -la\n')await handle.sendInput('exit\n')
// Wait for completionconst result = await handle.wait()console.log(`Session exited with code: ${result.exitCode}`)
// Clean upawait handle.disconnect()# Connect to an existing PTY sessionpty_handle = sandbox.process.connect_pty_session('my-session')pty_handle.send_input("echo 'Hello World'\n")pty_handle.send_input("exit\n")
# Handle outputpty_handle.each { |data| print data }
puts "Session exited with code: #{pty_handle.exit_code}"// Connect to an existing PTY sessionhandle, err := sandbox.Process.ConnectPty(ctx, "my-session")if err != nil { log.Fatal(err)}defer handle.Disconnect()
// Wait for connection to be establishedif err := handle.WaitForConnection(ctx); err != nil { log.Fatal(err)}
// Send commands to the existing sessionhandle.SendInput([]byte("pwd\n"))handle.SendInput([]byte("ls -la\n"))handle.SendInput([]byte("exit\n"))
// Read outputfor data := range handle.DataChan() { fmt.Print(string(data))}
// Wait for completionresult, err := handle.Wait(ctx)if err != nil { log.Fatal(err)}fmt.Printf("Session exited with code: %d\n", *result.ExitCode)import io.daytona.sdk.PtyCreateOptions;import io.daytona.sdk.PtyHandle;import io.daytona.sdk.PtyResult;import java.nio.charset.StandardCharsets;
PtyCreateOptions options = new PtyCreateOptions() .setOnData(chunk -> System.out.print(new String(chunk, StandardCharsets.UTF_8)));PtyHandle handle = sandbox.process.connectPty("my-session", options);handle.waitForConnection(30);
handle.sendInput("pwd\n");handle.sendInput("ls -la\n");handle.sendInput("exit\n");
PtyResult result = handle.waitForExit();System.out.println("Session exited with code: " + result.getExitCode());handle.disconnect();curl 'https://proxy.app.daytona.io/toolbox/{sandboxId}/process/pty/{sessionId}/connect'List PTY sessions
Section titled “List PTY sessions”List PTY sessions currently registered in the sandbox.
# List all PTY sessionssessions = sandbox.process.list_pty_sessions()
for session in sessions: print(f"Session ID: {session.id}") print(f"Active: {session.active}") print(f"Lazy start: {session.lazy_start}") print(f"Created: {session.created_at}")// List all PTY sessionsconst sessions = await sandbox.process.listPtySessions()
for (const session of sessions) { console.log(`Session ID: ${session.id}`) console.log(`Active: ${session.active}`) console.log(`Lazy start: ${session.lazyStart}`) console.log(`Created: ${session.createdAt}`) console.log('---')}# List all PTY sessionssessions = sandbox.process.list_pty_sessions
sessions.each do |session| puts "Session ID: #{session.id}" puts "Active: #{session.active}" puts "Terminal Size: #{session.cols}x#{session.rows}" puts '---'end// List all PTY sessionssessions, err := sandbox.Process.ListPtySessions(ctx)if err != nil { log.Fatal(err)}
for _, session := range sessions { fmt.Printf("Session ID: %s\n", session.Id) fmt.Printf("Active: %t\n", session.Active) fmt.Printf("Terminal Size: %dx%d\n", session.Cols, session.Rows) fmt.Println("---")}import io.daytona.toolbox.client.model.PtySessionInfo;import java.util.List;
List<PtySessionInfo> sessions = sandbox.process.listPtySessions();for (PtySessionInfo session : sessions) { System.out.println("Session ID: " + session.getId()); System.out.println("Active: " + session.getActive()); System.out.println("Lazy start: " + session.getLazyStart()); System.out.println("Created: " + session.getCreatedAt()); System.out.println("---");}curl 'https://proxy.app.daytona.io/toolbox/{sandboxId}/process/pty'Get PTY session info
Section titled “Get PTY session info”Get details about a specific PTY session.
# Get details about a specific PTY sessionsession_info = sandbox.process.get_pty_session_info("my-session")
print(f"Session ID: {session_info.id}")print(f"Active: {session_info.active}")print(f"Lazy start: {session_info.lazy_start}")print(f"Working Directory: {session_info.cwd}")print(f"Terminal Size: {session_info.cols}x{session_info.rows}")// Get details about a specific PTY sessionconst session = await sandbox.process.getPtySessionInfo('my-session')
console.log(`Session ID: ${session.id}`)console.log(`Active: ${session.active}`)console.log(`Lazy start: ${session.lazyStart}`)console.log(`Working Directory: ${session.cwd}`)console.log(`Terminal Size: ${session.cols}x${session.rows}`)# Get details about a specific PTY sessionsession_info = sandbox.process.get_pty_session_info('my-session')
puts "Session ID: #{session_info.id}"puts "Active: #{session_info.active}"puts "Lazy start: #{session_info.lazy_start}"puts "Working Directory: #{session_info.cwd}"puts "Terminal Size: #{session_info.cols}x#{session_info.rows}"// Get details about a specific PTY sessionsession, err := sandbox.Process.GetPtySessionInfo(ctx, "my-session")if err != nil { log.Fatal(err)}
fmt.Printf("Session ID: %s\n", session.Id)fmt.Printf("Active: %t\n", session.Active)fmt.Printf("Working Directory: %s\n", session.Cwd)fmt.Printf("Terminal Size: %dx%d\n", session.Cols, session.Rows)import io.daytona.toolbox.client.model.PtySessionInfo;
PtySessionInfo session = sandbox.process.getPtySessionInfo("my-session");System.out.println("Session ID: " + session.getId());System.out.println("Active: " + session.getActive());System.out.println("Lazy start: " + session.getLazyStart());System.out.println("Working Directory: " + session.getCwd());System.out.println("Terminal Size: " + session.getCols() + "x" + session.getRows());curl 'https://proxy.app.daytona.io/toolbox/{sandboxId}/process/pty/{sessionId}'Kill PTY session
Section titled “Kill PTY session”Kill a PTY session, terminating the shell process and removing the session from the sandbox.
# Kill a specific PTY sessionsandbox.process.kill_pty_session("my-session")
# Verify the session no longer existspty_sessions = sandbox.process.list_pty_sessions()for pty_session in pty_sessions: print(f"PTY session: {pty_session.id}")// Kill a specific PTY sessionawait sandbox.process.killPtySession('my-session')
// Verify the session is no longer activetry { const info = await sandbox.process.getPtySessionInfo('my-session') console.log(`Session still exists but active: ${info.active}`)} catch (error) { console.log('Session has been completely removed')}# Delete a specific PTY sessionsandbox.process.delete_pty_session('my-session')
# Verify the session no longer existssessions = sandbox.process.list_pty_sessionssessions.each do |session| puts "PTY session: #{session.id}"end// Kill a specific PTY sessionerr := sandbox.Process.KillPtySession(ctx, "my-session")if err != nil { log.Fatal(err)}
// Verify the session is no longer activesessions, err := sandbox.Process.ListPtySessions(ctx)if err != nil { log.Fatal(err)}
for _, session := range sessions { fmt.Printf("PTY session: %s\n", session.Id)}import io.daytona.toolbox.client.model.PtySessionInfo;import java.util.List;
sandbox.process.killPtySession("my-session");
List<PtySessionInfo> sessions = sandbox.process.listPtySessions();for (PtySessionInfo session : sessions) { System.out.println("PTY session: " + session.getId());}curl 'https://proxy.app.daytona.io/toolbox/{sandboxId}/process/pty/{sessionId}' \ --request DELETEResize PTY session
Section titled “Resize PTY session”Resize a PTY session, allowing you to change the terminal dimensions of an active PTY session.
from daytona.common.pty import PtySize
# Resize a PTY session to a larger terminalnew_size = PtySize(rows=40, cols=150)updated_info = sandbox.process.resize_pty_session("my-session", new_size)
print(f"Terminal resized to {updated_info.cols}x{updated_info.rows}")
# You can also use the PtyHandle's resize methodpty_handle.resize(new_size)// Resize a PTY session to a larger terminalconst updatedInfo = await sandbox.process.resizePtySession('my-session', 150, 40)console.log(`Terminal resized to ${updatedInfo.cols}x${updatedInfo.rows}`)
// You can also use the PtyHandle's resize methodawait ptyHandle.resize(150, 40) // cols, rows# Resize a PTY session to a larger terminalpty_size = Daytona::PtySize.new(rows: 40, cols: 150)session_info = sandbox.process.resize_pty_session('my-session', pty_size)
puts "Terminal resized to #{session_info.cols}x#{session_info.rows}"// Resize a PTY session to a larger terminalupdatedInfo, err := sandbox.Process.ResizePtySession(ctx, "my-session", types.PtySize{ Cols: 150, Rows: 40,})if err != nil { log.Fatal(err)}
fmt.Printf("Terminal resized to %dx%d\n", updatedInfo.Cols, updatedInfo.Rows)
// You can also use the PtyHandle's Resize methodinfo, err := handle.Resize(ctx, 150, 40)if err != nil { log.Fatal(err)}fmt.Printf("Terminal resized to %dx%d\n", info.Cols, info.Rows)sandbox.process.resizePtySession("my-session", 150, 40);
// Or resize using the handle (after createPty or connectPty)handle.resize(150, 40);curl 'https://proxy.app.daytona.io/toolbox/{sandboxId}/process/pty/{sessionId}/resize' \ --request POST \ --header 'Content-Type: application/json' \ --data '{ "cols": 150, "rows": 40}'Interactive commands
Section titled “Interactive commands”Handle interactive commands with PTY sessions, allowing you to handle interactive commands that require user input and can be resized during execution.
import timeimport threadingfrom daytona.common.pty import PtySize
def handle_pty_data(data: bytes): text = data.decode("utf-8", errors="replace") print(text, end="")
# Create PTY sessionpty_handle = sandbox.process.create_pty_session( id="interactive-session", pty_size=PtySize(cols=300, rows=100))
# Handle output while sending interactive inputthread = threading.Thread(target=pty_handle.wait, args=(handle_pty_data,))thread.start()
# Send interactive commandpty_handle.send_input('printf "Are you accepting the terms and conditions? (y/n): " && read confirm && if [ "$confirm" = "y" ]; then echo "You accepted"; else echo "You did not accept"; fi\n')time.sleep(1)pty_handle.send_input("y\n")
# Resize terminalpty_session_info = pty_handle.resize(PtySize(cols=210, rows=110))print(f"PTY session resized to {pty_session_info.cols}x{pty_session_info.rows}")
# Exit the sessionpty_handle.send_input('exit\n')thread.join()
print(f"Session completed with exit code: {pty_handle.exit_code}")import { Daytona, Sandbox } from '@daytona/sdk'
// Create PTY sessionconst ptyHandle = await sandbox.process.createPty({ id: 'interactive-session', cols: 300, rows: 100, onData: data => { const text = new TextDecoder().decode(data) process.stdout.write(text) },})
await ptyHandle.waitForConnection()
// Send interactive commandawait ptyHandle.sendInput( 'printf "Are you accepting the terms and conditions? (y/n): " && read confirm && if [ "$confirm" = "y" ]; then echo "You accepted"; else echo "You did not accept"; fi\n')await new Promise(resolve => setTimeout(resolve, 1000))await ptyHandle.sendInput('y\n')
// Resize terminalconst ptySessionInfo = await sandbox.process.resizePtySession( 'interactive-session', 210, 110)console.log( `\nPTY session resized to ${ptySessionInfo.cols}x${ptySessionInfo.rows}`)
// Exit the sessionawait ptyHandle.sendInput('exit\n')
// Wait for completionconst result = await ptyHandle.wait()console.log(`Session completed with exit code: ${result.exitCode}`)require 'daytona'
# Create PTY sessionpty_handle = sandbox.process.create_pty_session( id: 'interactive-session', pty_size: Daytona::PtySize.new(cols: 300, rows: 100))
# Handle output in a separate threadthread = Thread.new do pty_handle.each { |data| print data }end
# Send interactive commandpty_handle.send_input('printf "Are you accepting the terms and conditions? (y/n): " && read confirm && if [ "$confirm" = "y" ]; then echo "You accepted"; else echo "You did not accept"; fi' + "\n")sleep(1)pty_handle.send_input("y\n")
# Resize terminalpty_handle.resize(Daytona::PtySize.new(cols: 210, rows: 110))puts "\nPTY session resized"
# Exit the sessionpty_handle.send_input("exit\n")
# Wait for the thread to finishthread.join
puts "Session completed with exit code: #{pty_handle.exit_code}"// Create PTY sessionhandle, err := sandbox.Process.CreatePty(ctx, "interactive-session", options.WithCreatePtySize(types.PtySize{Cols: 300, Rows: 100}),)if err != nil { log.Fatal(err)}defer handle.Disconnect()
if err := handle.WaitForConnection(ctx); err != nil { log.Fatal(err)}
// Handle output in a goroutinego func() { for data := range handle.DataChan() { fmt.Print(string(data)) }}()
// Send interactive commandhandle.SendInput([]byte(`printf "Are you accepting the terms and conditions? (y/n): " && read confirm && if [ "$confirm" = "y" ]; then echo "You accepted"; else echo "You did not accept"; fi` + "\n"))time.Sleep(1 * time.Second)handle.SendInput([]byte("y\n"))
// Resize terminalinfo, err := handle.Resize(ctx, 210, 110)if err != nil { log.Fatal(err)}fmt.Printf("\nPTY session resized to %dx%d\n", info.Cols, info.Rows)
// Exit the sessionhandle.SendInput([]byte("exit\n"))
// Wait for completionresult, err := handle.Wait(ctx)if err != nil { log.Fatal(err)}fmt.Printf("Session completed with exit code: %d\n", *result.ExitCode)import io.daytona.sdk.PtyCreateOptions;import io.daytona.sdk.PtyHandle;import io.daytona.sdk.PtyResult;import java.nio.charset.StandardCharsets;
PtyCreateOptions options = new PtyCreateOptions() .setId("interactive-session") .setCols(300) .setRows(100) .setOnData(chunk -> System.out.print(new String(chunk, StandardCharsets.UTF_8)));
PtyHandle handle = sandbox.process.createPty(options);handle.waitForConnection(30);
handle.sendInput( "printf \"Are you accepting the terms and conditions? (y/n): \" && read confirm && if [ \"$confirm\" = \"y\" ]; then echo \"You accepted\"; else echo \"You did not accept\"; fi\n");Thread.sleep(1000);handle.sendInput("y\n");
sandbox.process.resizePtySession("interactive-session", 210, 110);System.out.println("\nPTY session resized");
handle.sendInput("exit\n");
PtyResult result = handle.waitForExit();System.out.println("Session completed with exit code: " + result.getExitCode());Long-running processes
Section titled “Long-running processes”Manage long-running processes with PTY sessions, allowing you to manage long-running processes that need to be monitored or terminated.
import timeimport threadingfrom daytona import Daytona, Sandboxfrom daytona.common.pty import PtySize
def handle_pty_data(data: bytes): text = data.decode("utf-8", errors="replace") print(text, end="")
# Create PTY sessionpty_handle = sandbox.process.create_pty_session( id="long-running-session", pty_size=PtySize(cols=120, rows=30))
# Start a long-running processpty_handle.send_input('while true; do echo "Running... $(date)"; sleep 1; done\n')
# Using thread and wait() method to handle PTY outputthread = threading.Thread(target=pty_handle.wait, args=(handle_pty_data, 10))thread.start()
time.sleep(3) # Let it run for a bit
print("Killing long-running process...")pty_handle.kill()
thread.join()
print(f"\nProcess terminated with exit code: {pty_handle.exit_code}")if pty_handle.error: print(f"Termination reason: {pty_handle.error}")import { Daytona, Sandbox } from '@daytona/sdk'
// Create PTY sessionconst ptyHandle = await sandbox.process.createPty({ id: 'long-running-session', cols: 120, rows: 30, onData: (data) => { const text = new TextDecoder().decode(data) process.stdout.write(text) },})
await ptyHandle.waitForConnection()
// Start a long-running processawait ptyHandle.sendInput('while true; do echo "Running... $(date)"; sleep 1; done\n')await new Promise(resolve => setTimeout(resolve, 3000)) // Let it run for a bit
console.log('Killing long-running process...')await ptyHandle.kill()
// Wait for terminationconst result = await ptyHandle.wait()console.log(`\nProcess terminated with exit code: ${result.exitCode}`)if (result.error) { console.log(`Termination reason: ${result.error}`)}require 'daytona'
# Create PTY sessionpty_handle = sandbox.process.create_pty_session( id: 'long-running-session', pty_size: Daytona::PtySize.new(cols: 120, rows: 30))
# Handle output in a separate threadthread = Thread.new do pty_handle.each { |data| print data }end
# Start a long-running processpty_handle.send_input("while true; do echo \"Running... $(date)\"; sleep 1; done\n")sleep(3) # Let it run for a bit
puts "Killing long-running process..."pty_handle.kill
thread.join
puts "\nProcess terminated with exit code: #{pty_handle.exit_code}"puts "Termination reason: #{pty_handle.error}" if pty_handle.error// Create PTY sessionhandle, err := sandbox.Process.CreatePty(ctx, "long-running-session", options.WithCreatePtySize(types.PtySize{Cols: 120, Rows: 30}),)if err != nil { log.Fatal(err)}defer handle.Disconnect()
if err := handle.WaitForConnection(ctx); err != nil { log.Fatal(err)}
// Handle output in a goroutinego func() { for data := range handle.DataChan() { fmt.Print(string(data)) }}()
// Start a long-running processhandle.SendInput([]byte(`while true; do echo "Running... $(date)"; sleep 1; done` + "\n"))time.Sleep(3 * time.Second) // Let it run for a bit
fmt.Println("Killing long-running process...")if err := handle.Kill(ctx); err != nil { log.Fatal(err)}
// Wait for terminationresult, err := handle.Wait(ctx)if err != nil { log.Fatal(err)}fmt.Printf("\nProcess terminated with exit code: %d\n", *result.ExitCode)if result.Error != nil { fmt.Printf("Termination reason: %s\n", *result.Error)}import io.daytona.sdk.PtyCreateOptions;import io.daytona.sdk.PtyHandle;import io.daytona.sdk.PtyResult;import java.nio.charset.StandardCharsets;
PtyCreateOptions options = new PtyCreateOptions() .setId("long-running-session") .setCols(120) .setRows(30) .setOnData(chunk -> System.out.print(new String(chunk, StandardCharsets.UTF_8)));
PtyHandle handle = sandbox.process.createPty(options);handle.waitForConnection(30);
handle.sendInput("while true; do echo \"Running... $(date)\"; sleep 1; done\n");Thread.sleep(3000);
System.out.println("Killing long-running process...");handle.kill();
PtyResult result = handle.waitForExit();System.out.println("\nProcess terminated with exit code: " + result.getExitCode());if (result.getError() != null) { System.out.println("Termination reason: " + result.getError());}Resource management
Section titled “Resource management”Manage resource leaks with PTY sessions, allowing you to always clean up PTY sessions to prevent resource leaks.
# Python: Use try/finallypty_handle = Nonetry: pty_handle = sandbox.process.create_pty_session(id="session", pty_size=PtySize(cols=120, rows=30)) # Do work...finally: if pty_handle: pty_handle.kill()// TypeScript: Use try/finallylet ptyHandletry { ptyHandle = await sandbox.process.createPty({ id: 'session', cols: 120, rows: 30, }) // Do work...} finally { if (ptyHandle) await ptyHandle.kill()}# Ruby: Use begin/ensurepty_handle = nilbegin pty_handle = sandbox.process.create_pty_session( id: 'session', pty_size: Daytona::PtySize.new(cols: 120, rows: 30) ) # Do work...ensure pty_handle&.disconnectend// Go: defer Disconnect to close the WebSocket without killing the shellhandle, err := sandbox.Process.CreatePty(ctx, "session", options.WithCreatePtySize(types.PtySize{Cols: 120, Rows: 30}),)if err != nil { log.Fatal(err)}defer handle.Disconnect()
// Do work...
// Call Kill to terminate the shell process when needed// handle.Kill(ctx)import io.daytona.sdk.PtyCreateOptions;import io.daytona.sdk.PtyHandle;
PtyHandle handle = null;try { handle = sandbox.process.createPty( new PtyCreateOptions().setId("session").setCols(120).setRows(30)); handle.waitForConnection(30); // Do work...} finally { if (handle != null) { handle.kill(); handle.disconnect(); }}PtyHandle methods
Section titled “PtyHandle methods”Daytona provides methods to interact with PTY sessions, allowing you to send input, resize the terminal, wait for completion, and manage the WebSocket connection to a PTY session.
Send input
Section titled “Send input”Send input to a PTY session, allowing you to send input data (keystrokes or commands) to the PTY session.
# Send a commandpty_handle.send_input("ls -la\n")
# Send user inputpty_handle.send_input("y\n")// Send a commandawait ptyHandle.sendInput('ls -la\n')
// Send raw bytesawait ptyHandle.sendInput(new Uint8Array([3])) // Ctrl+C# Send a commandpty_handle.send_input("ls -la\n")
# Send user inputpty_handle.send_input("y\n")// Send a commandhandle.SendInput([]byte("ls -la\n"))
// Send Ctrl+Chandle.SendInput([]byte{0x03})// Send a commandptyHandle.sendInput("ls -la\n");
// Send raw bytes (Ctrl+C)ptyHandle.sendInput(new byte[] { 0x03 });Wait for completion
Section titled “Wait for completion”Wait for a PTY process to exit and return the result.
# Wait with a callback for output datadef handle_data(data: bytes): print(data.decode("utf-8", errors="replace"), end="")
result = pty_handle.wait(on_data=handle_data, timeout=30)print(f"Exit code: {result.exit_code}")// Wait for process to completeconst result = await ptyHandle.wait()
if (result.exitCode === 0) { console.log('Process completed successfully')} else { console.log(`Process failed with code: ${result.exitCode}`) if (result.error) { console.log(`Error: ${result.error}`) }}# Wait by iterating over output (blocks until PTY session ends)pty_handle.each { |data| print data }
if pty_handle.exit_code == 0 puts 'Process completed successfully'else puts "Process failed with code: #{pty_handle.exit_code}" puts "Error: #{pty_handle.error}" if pty_handle.errorend// Wait for process to completeresult, err := handle.Wait(ctx)if err != nil { log.Fatal(err)}
if result.ExitCode != nil && *result.ExitCode == 0 { fmt.Println("Process completed successfully")} else { fmt.Printf("Process failed with code: %d\n", *result.ExitCode) if result.Error != nil { fmt.Printf("Error: %s\n", *result.Error) }}import io.daytona.sdk.PtyResult;
PtyResult result = ptyHandle.waitForExit();
if (result.getExitCode() == 0) { System.out.println("Process completed successfully");} else { System.out.println("Process failed with code: " + result.getExitCode()); if (result.getError() != null) { System.out.println("Error: " + result.getError()); }}Wait for connection
Section titled “Wait for connection”Wait for the WebSocket connection to be established before sending input.
# Python handles connection internally during creation# No explicit wait needed// Wait for connection to be establishedawait ptyHandle.waitForConnection()
// Now safe to send inputawait ptyHandle.sendInput('echo "Connected!"\n')pty_handle.wait_for_connectionpty_handle.send_input("echo 'Connected!'\n")// Wait for connection to be establishedif err := handle.WaitForConnection(ctx); err != nil { log.Fatal(err)}
// Now safe to send inputhandle.SendInput([]byte("echo 'Connected!'\n"))// Wait for connection (timeout in seconds)ptyHandle.waitForConnection(30);
ptyHandle.sendInput("echo \"Connected!\"\n");Kill PTY process
Section titled “Kill PTY process”Kill a PTY process and terminate the session from the handle.
pty_handle.kill()// Kill a long-running processawait ptyHandle.kill()
// Wait to confirm terminationconst result = await ptyHandle.wait()console.log(`Process terminated with exit code: ${result.exitCode}`)# Kill a long-running processpty_handle.kill
puts "Process terminated with exit code: #{pty_handle.exit_code}"// Kill a long-running processif err := handle.Kill(ctx); err != nil { log.Fatal(err)}
// Wait to confirm terminationresult, err := handle.Wait(ctx)if err != nil { log.Fatal(err)}fmt.Printf("Process terminated with exit code: %d\n", *result.ExitCode)import io.daytona.sdk.PtyResult;
ptyHandle.kill();
PtyResult result = ptyHandle.waitForExit();System.out.println("Process terminated with exit code: " + result.getExitCode());Resize from handle
Section titled “Resize from handle”Resize the PTY terminal dimensions directly from the handle.
from daytona.common.pty import PtySize
pty_handle.resize(PtySize(cols=120, rows=30))// Resize to 120x30await ptyHandle.resize(120, 30)# Resize to 120x30pty_handle.resize(Daytona::PtySize.new(cols: 120, rows: 30))// Resize to 120x30info, err := handle.Resize(ctx, 120, 30)if err != nil { log.Fatal(err)}fmt.Printf("Resized to %dx%d\n", info.Cols, info.Rows)// Resize to 120x30ptyHandle.resize(120, 30);Disconnect
Section titled “Disconnect”Disconnect from a PTY session and clean up resources without killing the process.
# Python: Use kill() to terminate, or let the handle go out of scope// Always clean up when donetry { // ... use PTY session} finally { await ptyHandle.disconnect()}begin # ... use PTY sessionensure pty_handle.disconnectend// Always clean up when done using deferhandle, err := sandbox.Process.CreatePty(ctx, "session")if err != nil { log.Fatal(err)}defer handle.Disconnect()
// ... use PTY sessiontry { // ... use PTY session} finally { ptyHandle.disconnect();}Check connection status
Section titled “Check connection status”Check if a PTY session is still connected.
# Python: Check by attempting operations or using session infosession_info = sandbox.process.get_pty_session_info("my-session")print(f"Session active: {session_info.active}")if (ptyHandle.isConnected()) { console.log('PTY session is active')}# Ruby: Check by using session infosession_info = sandbox.process.get_pty_session_info('my-session')puts 'PTY session is active' if session_info.activeif handle.IsConnected() { fmt.Println("PTY session is active")}if (ptyHandle.isConnected()) { System.out.println("PTY session is active");}Exit code and error
Section titled “Exit code and error”Access the exit code and error message after a PTY process terminates.
# After iteration or wait completesprint(f"Exit code: {pty_handle.exit_code}")if pty_handle.error: print(f"Error: {pty_handle.error}")// Access via getters after process terminatesconsole.log(`Exit code: ${ptyHandle.exitCode}`)if (ptyHandle.error) { console.log(`Error: ${ptyHandle.error}`)}# Access after process terminatesputs "Exit code: #{pty_handle.exit_code}"puts "Error: #{pty_handle.error}" if pty_handle.error// Access via methods after process terminatesif exitCode := handle.ExitCode(); exitCode != nil { fmt.Printf("Exit code: %d\n", *exitCode)}if errMsg := handle.Error(); errMsg != nil { fmt.Printf("Error: %s\n", *errMsg)}import io.daytona.sdk.PtyResult;
PtyResult result = ptyHandle.waitForExit();System.out.println("Exit code: " + result.getExitCode());if (result.getError() != null) { System.out.println("Error: " + result.getError());}Iterate over output (Python)
Section titled “Iterate over output (Python)”Iterate over a PTY handle to receive output data.
# Iterate over PTY outputfor data in pty_handle: text = data.decode("utf-8", errors="replace") print(text, end="")
print(f"Session ended with exit code: {pty_handle.exit_code}")// TypeScript uses the onData callback insteadconst ptyHandle = await sandbox.process.createPty({ id: 'my-session', onData: (data) => { const text = new TextDecoder().decode(data) process.stdout.write(text) },})# Iterate over PTY outputpty_handle.each do |data| print dataend
puts "Session ended with exit code: #{pty_handle.exit_code}"// Go uses a channel to receive output datafor data := range handle.DataChan() { fmt.Print(string(data))}
// Or use as io.Readerio.Copy(os.Stdout, handle)
fmt.Printf("Session ended with exit code: %d\n", *handle.ExitCode())import io.daytona.sdk.PtyCreateOptions;import java.nio.charset.StandardCharsets;
PtyHandle ptyHandle = sandbox.process.createPty( new PtyCreateOptions() .setId("my-session") .setOnData(chunk -> System.out.print(new String(chunk, StandardCharsets.UTF_8))));Error handling
Section titled “Error handling”Monitor exit codes and handle errors appropriately with PTY sessions.
# Python: Check exit codesresult = pty_handle.wait()if result.exit_code != 0: print(f"Command failed: {result.exit_code}") print(f"Error: {result.error}")// TypeScript: Check exit codesconst result = await ptyHandle.wait()if (result.exitCode !== 0) { console.log(`Command failed: ${result.exitCode}`) console.log(`Error: ${result.error}`)}# Ruby: Check exit codes# The handle blocks until the PTY session completespty_handle.each { |data| print data }
if pty_handle.exit_code != 0 puts "Command failed: #{pty_handle.exit_code}" puts "Error: #{pty_handle.error}"end// Go: Check exit codesresult, err := handle.Wait(ctx)if err != nil { log.Fatal(err)}
if result.ExitCode != nil && *result.ExitCode != 0 { fmt.Printf("Command failed: %d\n", *result.ExitCode) if result.Error != nil { fmt.Printf("Error: %s\n", *result.Error) }}import io.daytona.sdk.PtyResult;
PtyResult result = ptyHandle.waitForExit();if (result.getExitCode() != 0) { System.out.println("Command failed: " + result.getExitCode()); if (result.getError() != null) { System.out.println("Error: " + result.getError()); }}