Skip to content

feat: terminal steps - tty/interactive fields and exec step type - #2602

Merged
Andriy Knysh (aknysh) merged 26 commits into
mainfrom
osterman/custom-command-raw-tty
Jun 15, 2026
Merged

Andriy Knysh (aknysh) merged 26 commits into
mainfrom
osterman/custom-command-raw-tty

Conversation

@osterman

@osterman Erik Osterman (Cloud Posse) (osterman) commented Jun 11, 2026 •

Copy link
Copy Markdown
Member

what

Terminal steps for custom commands and workflows — three related capabilities:

  • interactive: true — attach host stdin and let the step own Ctrl-C. Atmos suspends its SIGINT-exit handler while the step runs (new pkg/signals suspension registry consulted by the main.go signal handler).
  • tty: true — allocate a pseudo-terminal (reusing pkg/terminal/pty, same engine as atmos devcontainer attach). The command sees a real TTY; secret masking is applied to PTY output. With interactive: true, the host terminal switches to raw mode so Ctrl-C flows through the PTY to the child.
  • type: exec — replace the Atmos process entirely (shell exec semantics): execve of the system shell on Unix (env, working directory, and terminal inherited natively; ATMOS_SHLVL unchanged), spawn-and-propagate-exit-code emulation on Windows. Validated to be the final step; tty/interactive/retry/timeout/output are rejected on exec steps.

Architecture: all logic lives in narrow packages — pkg/process (RunShellStep routing, RunShellSession, ReplaceShellSession), pkg/schema (validation), pkg/signals (interrupt suspension). cmd/ and internal/exec contain only inline switch-case call sites; pkg/runner and the step handler share the same routing.

Also fixes in pkg/terminal/pty found along the way:

  • stdin copier no longer blocks completion (it's detached, docker-CLI pattern)
  • session teardown is bounded: when grandchildren (e.g. aws ssm's session-manager-plugin) keep the PTY slave open after the child exits, output drains on a 1s deadline instead of hanging with the terminal in raw mode
  • DisableStdinForward for -t-without--i semantics

why

Custom commands had no way to hand the terminal to an interactive process:

commands:
  - name: ssh
    steps:
      - type: shell
        command: "exec aws ssm start-session --target {{ .Arguments.instance_id }}"

ran the SSM session as a piped, masked subprocess: full-screen rendering broke, and Ctrl-C inside the session killed Atmos itself (global SIGINT handler exits 130), killing the orphaned session with SIGPIPE.

With this change:

commands:
  - name: ssh
    steps:
      - type: shell
        tty: true
        interactive: true
        command: "aws ssm start-session --target {{ .Arguments.instance_id }}"

behaves like docker run -it (supervised: masking preserved, more steps can follow), and:

      - type: exec
        command: "aws ssm start-session --target {{ .Arguments.instance_id }}"

hands the process over entirely (launcher: native job control, zero proxy overhead, must be the last step).

references

  • Reported in SweetOps Slack (SSM session via custom command gets a mangled terminal and dies with SIGPIPE on Ctrl-C); teardown hang + raw-terminal-after-exit reproduced live on this PR and fixed
  • Docs: Interactive and TTY Steps

🤖 Generated with Claude Code

…suspension

Add Interactive/Tty bools to Task and WorkflowStep with converter mappings.
Add pkg/signals with a nestable interrupt-exit suspension counter, consulted
by the main signal handler so foreground interactive steps own Ctrl-C.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ward

The stdin copy goroutine was joined via WaitGroup, but io.Copy from a
terminal only returns on the next read after the PTY closes - so ExecWithPTY
hung until a keypress after the child exited. Detach the stdin copier
(docker-CLI pattern) and drain IO errors non-blockingly.

DisableStdinForward supports docker's -t-without-i semantics: the child gets
a TTY but host input is not forwarded and the host terminal stays cooked.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Runs a shell command attached to the user's terminal: under a PTY when
supported (masking preserved, raw mode routes Ctrl-C to the child for
interactive sessions), otherwise via direct fd inheritance with a visible
warning that masking is unavailable. Interactive sessions suspend the global
SIGINT-exit handler; child exit codes propagate as ExitCodeError.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…l sessions

Shell steps with tty: true attach the user's terminal via RunShellSession;
interactive: true suspends the Atmos SIGINT-exit handler so the step owns
Ctrl-C. Plain steps keep the existing masked shell-interpreter path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Workflow shell steps with tty: true attach the user's terminal via
RunShellSession (output modes don't apply); interactive: true suspends the
Atmos SIGINT-exit handler so the step owns Ctrl-C.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…step handler

Shell tasks with tty: true attach the user's terminal via RunShellSession
(no capturable output; exit code recorded in metadata). Interactive tasks
attach host stdin, force raw output mode (buffered modes would hide prompts),
and suspend the Atmos SIGINT-exit handler while running.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@atmos-pro

atmos-pro Bot commented Jun 11, 2026 •

Copy link
Copy Markdown
Contributor

Tip

Atmos Pro  

No affected stacks workflow was detected for this pull request.
If this is expected, no action is needed.
Learn More. Ask AI.

@osterman Erik Osterman (Cloud Posse) (osterman) added the minor New features that do not break anything label Jun 11, 2026
@github-actions github-actions Bot added the size/l Large size PR label Jun 11, 2026
@github-actions

github-actions Bot commented Jun 11, 2026 •

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues found.

Scanned Files

None

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 11, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 33ce2027-3458-4f6b-96ab-bbe563c6bc77

📥 Commits

Reviewing files that changed from the base of the PR and between 9c073bd and 887a988.

📒 Files selected for processing (3)
  • cmd/cmd_utils.go
  • errors/errors.go
  • pkg/datafetcher/schema/atmos/manifest/1.0.json
💤 Files with no reviewable changes (2)
  • pkg/datafetcher/schema/atmos/manifest/1.0.json
  • errors/errors.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/cmd_utils.go

📝 Walkthrough

Walkthrough

Adds terminal-aware shell execution (tty, interactive) for full-screen programs and interactive sessions, introduces exec step type for process replacement, validates exec constraints, suspends interrupt handling during foreground sessions, refactors PTY stdin/output handling, wires new paths through runners and workflows, and documents behavior.

Changes

Terminal-Aware Steps and Process Replacement

Layer / File(s) Summary
Schema and validation contracts
pkg/schema/task.go, pkg/schema/workflow.go, pkg/schema/task_validate.go, pkg/schema/task_validate_test.go, pkg/datafetcher/schema/*
Adds interactive and tty boolean fields to Task and WorkflowStep, introduces TaskTypeExec constant, updates JSON schema definitions, implements task↔workflow field conversions, and provides ValidateExecTasks and ValidateExecWorkflowSteps validation functions that enforce exec-step placement and field restrictions with comprehensive test coverage.
Signal suspension and exit cleanup
pkg/signals/interrupt.go, pkg/signals/cleanup.go, pkg/signals/interrupt_test.go, main.go, main_test.go, errors/errors.go, errors/error_funcs.go, errors/error_funcs_test.go
Adds SuspendInterruptExit() and InterruptExitSuspended() for nestable Ctrl-C deferral, RegisterExitCleanup() and RunExitCleanups() for signal-handler cleanup execution, refactors main signal loop to check suspension state before exit and run cleanups, implements silent exit-code detection to suppress themed error rendering, and adds signal/cleanup/silent-exit tests.
PTY runtime and terminal setup
pkg/terminal/pty/pty.go, pkg/terminal/pty/pty_test.go, pkg/terminal/pty/setup_unix.go, pkg/terminal/pty/setup_windows.go
Adds DisableStdinForward flag for optional stdin forwarding, detaches stdin copier from completion wait group to allow prompt return, bounded output draining with timeout on completion, conditional raw-mode terminal setup (only when requested and stdin is TTY), exit-cleanup based terminal restoration on both Unix and Windows, and regression tests for stdin handling, grandchild PTY hold, and drain behavior.
Shell session execution
pkg/process/shell_session.go, pkg/process/shell_session_test.go, pkg/process/shell_command_unix.go, pkg/process/shell_command_windows.go, pkg/process/exit_status_unix.go, pkg/process/exit_status_windows.go
Introduces ShellSessionSpec configuration, RunShellStep dispatcher routing tty/interactive to session path or fallback, RunShellSession orchestrator with PTY/attached mode selection, ATMOS_SHLVL incrementation, secret masking, OS-specific shell commands (sh -c / cmd /S /C), signal-convention exit code normalization (128 + signal), and hermetic subprocess tests covering attached/PTY/interactive/dry-run/cancellation/signal paths.
Process replacement (exec)
pkg/process/exec_replace.go, pkg/process/exec_replace_unix.go, pkg/process/exec_replace_windows.go, pkg/process/exec_replace_test.go
Defines ExecSpec for exec-step configuration, prepareExec handling dry-run/working-directory/environment, platform-specific ReplaceShellSession (Unix: syscall.Exec replacement; Windows: spawn and wait), error wrapping via ErrProcessStartFailed, and subprocess test harness verifying exit codes, environment inheritance, working-directory correctness, dry-run behavior, and shell/execve failure paths.
Runner and step handler integration
pkg/runner/runner.go, pkg/runner/runner_test.go, pkg/runner/step/shell.go, pkg/runner/step/shell_test.go
Routes TaskTypeShell through new runShellTask helper using RunShellStep, adds upfront ValidateExecTasks in RunAll, updates ShellHandler.Execute and ExecuteWithWorkflow to route tty/interactive steps to session path with empty captured output, expands getExitCode to recognize typed ExitCodeError, and adds tests for tty, interactive, and exec task routing.
Workflow and custom command wiring
internal/exec/workflow_utils.go, internal/exec/workflow_utils_test.go, cmd/cmd_utils.go, tests/snapshots/*, tests/test-cases/workflows.yaml
Adds upfront exec workflow validation before execution with ValidateExecWorkflowSteps, routes workflow shell steps through RunShellStep with session specs, routes custom command shell steps through RunShellStep and exec steps through ReplaceShellSession, updates invalid-step type hints to list exec as valid, detects and propagates silent exit-code errors without themed wrapping, and updates test snapshots/cases.
Documentation and roadmap
website/blog/2026-06-11-tty-interactive-steps.mdx, website/docs/cli/configuration/commands.mdx, website/docs/workflows/workflows.mdx, website/src/data/roadmap.js
Adds comprehensive documentation for command and workflow tty and interactive fields with behavior descriptions and examples, documents exec step type with constraints and platform-specific notes, publishes a blog post explaining terminal takeover benefits and limitations, and updates roadmap milestone progress and metadata.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

  • cloudposse/atmos#1763: Relates to the silent exit-code path that suppresses error rendering for terminal-handoff steps, extending earlier error-handling infrastructure.
  • cloudposse/atmos#1229: Overlaps in internal/exec/workflow_utils.go where this PR adds exec validation and shell routing alongside prior refactoring of error-handling pathways.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 39.02% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main features added: terminal-step capabilities with tty/interactive fields and exec step type support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch osterman/custom-command-raw-tty

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/custom_command_shell_step_test.go`:
- Around line 78-87: The test currently shells out to the Unix `sleep` binary
and skips Windows; replace that with a Go-native test helper subprocess: create
a small helper program (invoked via os.Executable()) that reads a duration arg
and calls time.Sleep(duration) then exits, and update the test to invoke that
helper (e.g., exec.Command(exePath, "helper-sleep", "500ms")) instead of "sleep
0.5"; keep the same concurrency pattern using done channel and the same step
(schema.Task with TypeShell and Interactive) and call executeShellStep with the
helper command string so the timing check runs cross-platform without skipping.
Ensure the helper distinguishes mode by args (e.g., "helper-sleep") so normal
test runs unaffected.

In `@pkg/process/shell_session.go`:
- Around line 61-70: RunShellSession currently calls u.GetNextShellLevel()
(which parses ATMOS_SHLVL) before checking spec.DryRun; move the DryRun check
earlier or conditionally call GetNextShellLevel only when not spec.DryRun to
avoid failing on malformed ATMOS_SHLVL for dry-runs—update RunShellSession so
the early return for spec.DryRun happens before invoking u.GetNextShellLevel()
(or wrap the call in if !spec.DryRun { shellLevel, err := u.GetNextShellLevel()
... }) while keeping the rest of the logic intact.

In `@pkg/runner/runner.go`:
- Around line 163-173: The TTY branch is passing Options.Env straight into
ShellSessionSpec.Env which RunShellSession treats as the full environment,
causing system vars (PATH, HOME, AWS creds, etc.) to be lost; import the stdlib
os package and set ShellSessionSpec.Env to a merged environment (start with
os.Environ() and append/override with opts.Env entries) before calling
process.RunShellSession so that Options.Env remains additive rather than
replacing the ambient environment.

In `@pkg/runner/step/shell.go`:
- Around line 72-75: The code only routes terminal-attached steps (step.Tty) to
the new shell session path; update Execute and ExecuteWithWorkflow (and the
other similar checks around executeTtyStep) to also treat interactive steps the
same way by checking step.Interactive (or the struct field name used for
interactive) in addition to step.Tty and calling the
RunShellSession/executeTtyStep path so the Windows-capable fallback in
pkg/process/shell_session.go is used for interactive:true without a tty; locate
checks in Execute, ExecuteWithWorkflow and the other occurrences referenced (the
blocks calling h.executeTtyStep or RunShellSession) and extend their condition
to include interactive.

In `@pkg/terminal/pty/pty_test.go`:
- Around line 335-399: Replace the shell-based child in
TestExecWithPTY_DisableStdinForward (and any other tests using "sh") with a
Go-native test helper subprocess: add a test-only child mode triggered by an env
var (e.g., PTY_TEST_CHILD) in this test file that, when set, performs an
explicit blocking/non-blocking stdin read (with a short timeout) and prints
deterministic markers like "INPUT-RECEIVED" vs "NO-INPUT". Invoke that child by
using os.Executable() and exec.Command(os.Executable(), ...) with the env flag
set, pass Options (e.g., DisableStdinForward) to ExecWithPTY, and assert on
those markers to verify that host stdin is/ is not forwarded; keep
TestExecWithPTY_ReturnsWithBlockedStdin using neverEOFReader but also use
os.Executable() child if it needs to read/exit deterministically. Ensure you
update references to ExecWithPTY, Options.DisableStdinForward, and
neverEOFReader in the tests so they call the new Go-native child helper instead
of "sh".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6ccf3f67-f3cd-499e-9a1b-4620d5fc67bd

📥 Commits

Reviewing files that changed from the base of the PR and between 06e0196 and 8fd822b.

📒 Files selected for processing (28)
  • cmd/cmd_utils.go
  • cmd/custom_command_shell_step.go
  • cmd/custom_command_shell_step_test.go
  • internal/exec/workflow_utils.go
  • internal/exec/workflow_utils_test.go
  • main.go
  • main_test.go
  • pkg/datafetcher/schema/atmos/manifest/1.0.json
  • pkg/datafetcher/schema/config/global/1.0.json
  • pkg/process/shell_session.go
  • pkg/process/shell_session_test.go
  • pkg/runner/runner.go
  • pkg/runner/runner_test.go
  • pkg/runner/step/shell.go
  • pkg/runner/step/shell_test.go
  • pkg/schema/task.go
  • pkg/schema/task_test.go
  • pkg/schema/workflow.go
  • pkg/signals/interrupt.go
  • pkg/signals/interrupt_test.go
  • pkg/terminal/pty/pty.go
  • pkg/terminal/pty/pty_test.go
  • pkg/terminal/pty/setup_unix.go
  • pkg/terminal/pty/setup_windows.go
  • website/blog/2026-06-11-tty-interactive-steps.mdx
  • website/docs/cli/configuration/commands.mdx
  • website/docs/workflows/workflows.mdx
  • website/src/data/roadmap.js

Comment thread cmd/custom_command_shell_step_test.go Outdated
Comment thread pkg/process/shell_session_test.go Outdated
Comment thread pkg/process/shell_session.go Outdated
Comment thread pkg/runner/runner.go Outdated
Comment thread pkg/runner/step/shell.go Outdated
Comment thread pkg/terminal/pty/pty_test.go
@codecov

codecov Bot commented Jun 11, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.42857% with 44 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.47%. Comparing base (7fc9c3e) to head (887a988).

Files with missing lines Patch % Lines
main.go 41.17% 9 Missing and 1 partial ⚠️
cmd/cmd_utils.go 52.63% 8 Missing and 1 partial ⚠️
pkg/process/shell_session.go 87.50% 5 Missing and 3 partials ⚠️
pkg/terminal/pty/pty.go 82.85% 5 Missing and 1 partial ⚠️
pkg/terminal/pty/setup_unix.go 33.33% 4 Missing and 2 partials ⚠️
errors/error_funcs.go 60.00% 1 Missing and 1 partial ⚠️
internal/exec/workflow_utils.go 93.33% 1 Missing and 1 partial ⚠️
pkg/process/exec_replace_unix.go 91.66% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #2602      +/-   ##
==========================================
+ Coverage   79.40%   79.47%   +0.06%     
==========================================
  Files        1347     1355       +8     
  Lines      127229   127551     +322     
==========================================
+ Hits       101031   101373     +342     
+ Misses      20549    20523      -26     
- Partials     5649     5655       +6     
Flag Coverage Δ
unittests 79.47% <87.42%> (+0.06%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
errors/errors.go 100.00% <ø> (ø)
pkg/process/exec_replace.go 100.00% <100.00%> (ø)
pkg/process/exit_status_unix.go 100.00% <100.00%> (ø)
pkg/process/shell_command_unix.go 100.00% <100.00%> (ø)
pkg/runner/runner.go 83.87% <100.00%> (+4.99%) ⬆️
pkg/runner/step/shell.go 84.61% <100.00%> (+3.89%) ⬆️
pkg/schema/task.go 94.70% <100.00%> (+0.12%) ⬆️
pkg/schema/task_validate.go 100.00% <100.00%> (ø)
pkg/signals/cleanup.go 100.00% <100.00%> (ø)
pkg/signals/interrupt.go 100.00% <100.00%> (ø)
... and 8 more

... and 8 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Steps with type: exec hand the process over entirely (shell exec semantics):
execve of the system shell on Unix (inheriting env, working directory, and
the terminal natively; ATMOS_SHLVL unchanged), spawn-and-propagate-exit-code
emulation on Windows. Exec steps are validated to be the final step and must
not set supervisor-only fields (tty, interactive, retry, timeout, output).
Wired into custom commands, workflows, and the task runner.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ss-platform test helpers

Interactive (non-tty) steps now use the shell session path so suspension and
platform-aware shell selection live in one place (pkg/process); dry-run is
checked before shell-level validation; the runner env inherits os.Environ.
Tests use the test binary as a cross-platform subprocess helper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Interactive (non-tty) sessions always attach the real streams, so missing
masking is inherent there - debug-log it instead of warning on every step.
The visible warning remains for the unexpected case: tty requested but PTY
unavailable on the platform.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Move shell-family step routing into pkg/process.RunShellStep (terminal steps
to sessions, plain steps via caller fallback) and default masking wiring
inside RunShellSession. Delete the cmd/ step helper file and the internal/exec
helper - both locations now contain only inline switch-case call sites; the
runner and step handler use the same shared routing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When the session command exits, grandchildren that inherited the PTY slave
(e.g. aws ssm's session-manager-plugin) can keep it open, so the output
copier never gets EIO: atmos hung with the terminal in raw mode (no prompt
until a stray keypress, no echo afterwards). Drain output on a 1s deadline
after child exit, forcing the pending read to return so the terminal is
restored promptly.

Reported-by: live SSM session test on PR #2602

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The test binary used as a PTY child links TUI libraries that emit OSC/DSR
terminal queries when stdout is a TTY and block ~5s per query waiting for
replies no test PTY sends - hanging three pty tests past their deadlines and
silently adding ~15s to the process suite. TERM=dumb/NO_COLOR in the child
env suppresses the queries (pty suite 25s+fail -> 1.5s; process 16.6s -> 1.6s).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@osterman Erik Osterman (Cloud Posse) (osterman) changed the title feat: docker-style tty/interactive fields for custom command and workflow steps feat: terminal steps - tty/interactive fields and exec step type Jun 11, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/runner/runner.go (1)

163-168: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Comment must end with a period.

The multi-line comment for runShellTask is missing a period at the end of the last line.

📝 Proposed fix
 // Tasks with `tty: true` attach to the user's terminal (PTY when supported).
 // Tasks with `interactive: true` let the child process own Ctrl-C: the Atmos
 // SIGINT-exit handler is suspended while the task runs. Plain tasks delegate
-// to the CommandRunner
+// to the CommandRunner.
 func runShellTask(ctx context.Context, task *Task, runner CommandRunner, opts *Options, dir string) error {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/runner/runner.go` around lines 163 - 168, The doc comment for
runShellTask is missing a final period; update the multi-line comment above the
runShellTask function (the line ending with "delegate to the CommandRunner") to
end with a period so the comment finishes with proper punctuation.

Source: Coding guidelines

🧹 Nitpick comments (1)
pkg/schema/task_validate_test.go (1)

114-157: ⚡ Quick win

Add workflow invalid-field cases for tty, interactive, and retry.

ValidateExecWorkflowSteps enforces these too, but the workflow table currently pins only timeout/output. Adding these rows will lock the full workflow contract and prevent projection regressions.

Suggested test rows.
 		{
+			name: "exec with tty",
+			steps: []WorkflowStep{
+				{Type: TaskTypeExec, Command: "psql", Tty: true},
+			},
+			wantErr: ErrExecStepInvalidField,
+		},
+		{
+			name: "exec with interactive",
+			steps: []WorkflowStep{
+				{Type: TaskTypeExec, Command: "psql", Interactive: true},
+			},
+			wantErr: ErrExecStepInvalidField,
+		},
+		{
+			name: "exec with retry",
+			steps: []WorkflowStep{
+				{Type: TaskTypeExec, Command: "psql", Retry: &RetryConfig{}},
+			},
+			wantErr: ErrExecStepInvalidField,
+		},
+		{
 			name: "exec with timeout string",
 			steps: []WorkflowStep{
 				{Type: TaskTypeExec, Command: "psql", Timeout: "30s"},
 			},

As per coding guidelines, "Every new feature must include comprehensive unit tests targeting >80% code coverage for all packages."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/schema/task_validate_test.go` around lines 114 - 157, Add missing
invalid-field test rows to TestValidateExecWorkflowSteps: for WorkflowStep
entries with Type: TaskTypeExec set the Tty flag (Tty: true), the Interactive
flag (Interactive: true), and a non-zero Retry value (e.g., Retry: 1), and
assert wantErr is ErrExecStepInvalidField for each; this ensures
ValidateExecWorkflowSteps rejects exec steps that have Tty, Interactive, or
Retry set (reference TestValidateExecWorkflowSteps, WorkflowStep, TaskTypeExec,
and ErrExecStepInvalidField).

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/schema/task.go`:
- Around line 19-21: Update the comment for the Task.Type field to mention the
new TaskTypeExec value in addition to the existing shell/atmos type; locate the
Task.Type field comment (referencing Task.Type) in pkg/schema/task.go and edit
the docstring so it lists both the existing shell/atmos semantics and the new
"exec" semantics (TaskTypeExec) so the comment accurately reflects the supported
task types.

---

Outside diff comments:
In `@pkg/runner/runner.go`:
- Around line 163-168: The doc comment for runShellTask is missing a final
period; update the multi-line comment above the runShellTask function (the line
ending with "delegate to the CommandRunner") to end with a period so the comment
finishes with proper punctuation.

---

Nitpick comments:
In `@pkg/schema/task_validate_test.go`:
- Around line 114-157: Add missing invalid-field test rows to
TestValidateExecWorkflowSteps: for WorkflowStep entries with Type: TaskTypeExec
set the Tty flag (Tty: true), the Interactive flag (Interactive: true), and a
non-zero Retry value (e.g., Retry: 1), and assert wantErr is
ErrExecStepInvalidField for each; this ensures ValidateExecWorkflowSteps rejects
exec steps that have Tty, Interactive, or Retry set (reference
TestValidateExecWorkflowSteps, WorkflowStep, TaskTypeExec, and
ErrExecStepInvalidField).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a460033f-2d27-425b-bfe0-622aa6094578

📥 Commits

Reviewing files that changed from the base of the PR and between 8fd822b and 8fde4e9.

📒 Files selected for processing (18)
  • cmd/cmd_utils.go
  • internal/exec/workflow_utils.go
  • internal/exec/workflow_utils_test.go
  • pkg/process/exec_replace.go
  • pkg/process/exec_replace_test.go
  • pkg/process/exec_replace_unix.go
  • pkg/process/exec_replace_windows.go
  • pkg/process/shell_session.go
  • pkg/process/shell_session_test.go
  • pkg/process/shell_step_test.go
  • pkg/runner/runner.go
  • pkg/runner/runner_test.go
  • pkg/runner/step/shell.go
  • pkg/runner/step/shell_test.go
  • pkg/schema/task.go
  • pkg/schema/task_validate.go
  • pkg/schema/task_validate_test.go
  • pkg/terminal/pty/pty_test.go

Comment thread pkg/schema/task.go
… restore terminal on signal exit

Sessions whose child died from a signal now report 128+signal (e.g. 130)
instead of Go's -1 ('subcommand exited with code -1'). The PTY terminal
restore is also registered as a signal-exit cleanup: os.Exit in the signal
handler skips defers, which could leave the host terminal in raw mode when
atmos was signalled mid-session.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 12, 2026
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 12, 2026
…t-session hang)

A tty/interactive/exec session that exits non-zero produced a bare
ExitCodeError that Atmos rendered through the themed Glamour formatter. That
render triggers termenv's terminal queries (OSC 11 background color, DSR
cursor) and reads the reply from os.Stdin - but the session's stdin copier
steals it, so termenv blocks for its full ~5s timeout before the process
exits (Matt's '3-5s then code -1'; Erik's hang on a failed-command exit).

Mark session ExitCodeErrors Silent so the code propagates like a shell would,
with no themed error box and no terminal query. Honored in
CheckErrorPrintAndExit (custom commands), main.run (workflows), and the
workflow step-error wrapper. Before: 5.0s on non-zero exit; after: 3ms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@aknysh
Andriy Knysh (aknysh) merged commit 6c3ef6a into main Jun 15, 2026
59 checks passed
@aknysh
Andriy Knysh (aknysh) deleted the osterman/custom-command-raw-tty branch June 15, 2026 22:21
@atmos-pro

atmos-pro Bot commented Jun 15, 2026 •

Copy link
Copy Markdown
Contributor

Tip

Atmos Pro  

No affected stacks workflow was detected for this pull request.
If this is expected, no action is needed.
Learn More. Ask AI.

@github-actions

Copy link
Copy Markdown

These changes were released in v1.222.0-rc.1.

This branch was successfully deployed

1 active deployment
preview — 887a9881 Deployed Jun 15, 2026 by github-actions[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

minor New features that do not break anything size/l Large size PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants