Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ reviews:
drafts: false
base_branches:
- "main"
- "^osterman/starlark-execution-support$"
- "^osterman/starlark-runtime$"
- "^osterman/starlark-integration-core$"
- "^osterman/starlark-interpreter-step$"
- "^osterman/starlark-language-reference$"
- "^osterman/starlark-literal-script$"
- "^osterman/starlark-interpreter-fixes$"
- "^osterman/aal-step-library$"
- "^osterman/git-hook-script-steps$"
tools:
ast-grep:
essential_rules: true
Expand Down
14 changes: 14 additions & 0 deletions .github/mergify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,20 @@ pull_request_rules:
- workflow: test.yml
ref: "{{ head }}"

- name: Warn when a PR exceeds the CodeRabbit file limit
conditions:
- and: *is_open
- "#files>150"
actions:
comment:
message: |
> [!WARNING]
> #### This PR exceeds the 150-file CodeRabbit review limit.
>
> CodeRabbit cannot review this PR at its current size.
> Refactor it into a PR stack with fewer than 150 changed files per PR.
> Each PR should target the preceding branch so its diff includes only its own changes.

- name: Comment when size/xxl label is added
conditions:
- label=size/xxl
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Fix: Reuse logger output identities and resolve relative process paths once

**Date:** 2026-10-04

## Summary

Repeated logger setup reuses Charm renderer keys for the same destination. Processes launched with a relative working directory and relative PATH entries resolve the executable correctly.

## Context

CodeRabbit identified two issues in the execution support layer. Each file output acquired a fresh pointer wrapper, which Charm retained as a distinct renderer registry key. Invocation PATH lookup returned a relative executable path including the working directory, which `exec.Cmd` then interpreted relative to that directory a second time.

## Changes

The opaque logger writer now uses a comparable value when its destination supports equality. Repeated setup therefore produces the same renderer registry key without adding another cache. The stderr value resolves the current `os.Stderr` lazily. Non-comparable custom file-like writers retain their existing pointer wrapper behavior, preserving compatibility with Charm's map keys.

Process lookup resolves the invocation directory to an absolute path before searching its environment. The subprocess retains its requested working directory and original command arguments. Regression tests cover relative PATH entries, relative explicit executable paths, and an empty directory, using a copied Go test executable on all platforms.

## Validation

Both regressions failed before the fixes: repeated writer setup produced 20 distinct keys, and process startup attempted an executable path relative to the working directory twice. Tests also verify independent logger destinations after output changes and custom non-comparable writers.

- `go test -race -count=3 -timeout=10m -coverprofile=/tmp/atmos-support-review.cover ./pkg/logger ./pkg/process` passed; logger coverage was 96.5% and process coverage was 94.8%.
- The final package coverage run (`go test -count=1 -timeout=10m -coverprofile=.context/support-review.cover ./pkg/logger ./pkg/process`) passed with the same package coverage. Combined with the other changed-package tests, local changed-line coverage against `origin/main` was 585/615 (95.12%). This touched-package estimate is not full-suite coverage; CI Codecov remains authoritative.
- `go build ./...` passed.
- `./custom-gcl run --new-from-rev=HEAD ./pkg/logger/... ./pkg/process/...` passed with zero issues.

## Follow-ups

None.
70 changes: 70 additions & 0 deletions docs/fixes/2026-10-04-support-acceptance-ci-fixtures.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Fix: Align support-layer acceptance fixtures with terminal and transport behavior

**Date:** 2026-10-04

## Summary

Regenerate the terminal snapshot for lazy logger initialization and recognize
remote HTTP/2 response-stream cancellation in existing live GitHub test handling.
Session-cast tests now wait for the shell response rather than the PTY input echo.

## Context

PR #3263 acceptance run `37205411512` failed the `atmos list instances` TTY
snapshot on Linux and macOS. Lazy logger initialization removes redundant terminal
color queries; the displayed component table is unchanged. The same run's macOS
shard 7 saw GitHub cancel a response stream while `TestGetReleases` decoded it.
The existing transient-error classifier handled request-level `*url.Error`
failures but missed net/http's response-body stream error.

A later core race job (`37219051582`, shard 1) failed the session-cast end-to-end
test with `signal: killed` after 2.72 seconds. This was a process teardown failure,
not a race-detector report. Its substring wait for `ready` also matched the PTY's
echo of the typed `printf ready` command, before the helper process had started.
The test consequently entered the production two-second teardown deadline early.

## Changes

- Regenerate the list-instances TTY snapshot through the acceptance harness.
- Extend only the test helper to recognize the observed remote HTTP/2 CANCEL
message shape. Keep protocol errors, local cancellation, application errors,
and decoding errors as failures.
- Add positive and negative classifier regressions. No production request or
retry behavior changes.

- Match the shell's complete `ready` response line in all session fixture
handshakes. Preserve Windows LF and Unix PTY CRLF output handling.
- Exercise normal and delayed helper startup in the end-to-end test. The
test-only three-second delay deterministically exposes the premature echo
match; production session and teardown timeouts remain unchanged.

## Validation

- Reproduced the TTY snapshot mismatch locally; the only difference was the
redundant terminal-query prefix.
- `go test ./tests -run '^TestCLICommands/atmos_list_instances$' -count=1 -timeout=10m -regenerate-snapshots`
passed (38.098 seconds) and regenerated only the expected TTY snapshot.
- `go test ./tests -run '^TestCLICommands/atmos_list_instances$' -count=1 -timeout=10m`
passed against the regenerated snapshot (39.716 seconds).
- The new response-stream classification regression failed before the helper
fix for both observed peer-cancellation cases.
- `go test ./pkg/github -run '^TestGetReleases$' -count=1 -timeout=5m`
passed before the fix (12.790 seconds), consistent with a transient failure.
- `go test ./pkg/github -count=1 -timeout=10m` passed after the fix (18.184 seconds).
- `./custom-gcl run --new-from-rev=HEAD ./pkg/github` reported zero issues.

- Thirty repetitions of the original targeted race test passed (52.486 seconds),
so repetition alone did not reproduce or rule out the CI failure.
- The delayed-start regression then reproduced the same `signal: killed` failure
against the original substring wait (2.56 seconds); its normal-start case passed.

- `go test -race -count=3 -run 'TestCastHandlerExecutesSessionModeEndToEnd|TestRunCastSessionMode|TestCastHandlerSessionMode|TestCastSession' -timeout=5m ./pkg/runner/step`
passed with the complete-response waits (49.279 seconds).
- `go test -race -count=1 -timeout=10m ./pkg/runner/step` passed (49.727 seconds).
- `./custom-gcl run --new-from-rev=HEAD ./pkg/runner/step` reported zero issues;
the Windows step test binary cross-compiled successfully. Native Windows
execution remains covered by CI.

## Follow-ups

None.
38 changes: 38 additions & 0 deletions docs/fixes/2026-10-06-controller-runtime-notice-url.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Fix: Generate a stable controller-runtime license URL

**Date:** 2026-10-06

## Summary

Resolve controller-runtime's package-level license entry from its parent
module version so NOTICE generation produces the same URL without a network
lookup.

## Context

The dependency-review retry passed license verification, then failed the NOTICE
diff check. `go-licenses` resolved `sigs.k8s.io/controller-runtime/pkg` to a GitHub
license URL, while that job's checkout contained `Unknown`. The license URL
depends on whether upstream discovery succeeds, so regeneration can vary even
when the dependencies have not changed.

## Changes

Extend the generator's existing deterministic overrides with an optional parent
module for version lookup. The controller-runtime package entry uses the
resolved `sigs.k8s.io/controller-runtime` version and the repository's LICENSE.
Other override rules keep their existing version lookup.

## Validation

- The complete NOTICE-generator test suite passed, including both unresolved
and previously resolved input URLs.
- The current committed NOTICE contains the exact URL generated by
dependency-review job `112521195506`.
- A local full regeneration was stopped after ten minutes in the dependency
scan. The override is covered by the generator tests and will be exercised
by CI regeneration.

## Follow-ups

None.
53 changes: 53 additions & 0 deletions docs/fixes/2026-10-06-starlark-stack-review-bases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Fix: Review every Starlark stack layer automatically

**Date:** 2026-10-06

## Summary

Enable CodeRabbit automatic reviews for each base branch in the Starlark PR stack.

## Context

CodeRabbit's automatic-review configuration allowed `main` at the bottom of the
stack and only some intermediate bases in later layers. Updates targeting the
remaining branches therefore needed manual review requests.

## Changes

List each actual stack base explicitly in the review configuration. Keep the
existing review rules, required change-request workflow, and draft policy.

Also merge the current main branch, retaining its scaffold navigation links and
the YAML-function guide anchor from this stack. This resolves the README
conflict that prevented pull-request checks from starting.

The resumed review also identified two YAML loader inconsistencies:

- Limit included-script provenance to typed script steps and typed script-hook
payloads. Carry the parent and plain-data-section context through the tag walker
so variables, settings, environment, metadata, and other data gain no internal
provenance fields even when their contents resemble a script step. Child context
is derived once during indexed traversal and survives `!append` node rewrites.
- Expand configured key delimiters before decoding an already parsed YAML node,
matching the file entrypoint while retaining tag evaluation and source positions.

## Validation

- Compared the allowlist with the base branches reported by GitHub for all ten PRs.
- Checked the patch for whitespace errors.
- Automatic review results will be verified after the configuration reaches
each layer.

- Both YAML regressions failed before the fixes: plain data gained undeclared
provenance fields, and parsed-node decoding kept delimited keys literal.
- The focused include-provenance, script-source, parsed-node, and shared-tag-walker
tests passed with `GOMAXPROCS=4 go test -p 2 ./pkg/utils` and the targeted test
filter (7.627 seconds). Existing local, remote, queried, inline, and hook/list
cases retain their behavior. Fixtures now explicitly declare their hook type.
- Final focused tests, including appended data and appended script steps, passed
with coverage enabled (5.237 seconds).
- Formatted the affected Go files with `gofumpt`; `git diff --check` passed.

## Follow-ups

None.
17 changes: 17 additions & 0 deletions errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ import (
schemaPkg "github.com/cloudposse/atmos/pkg/schema"
)

// ErrStarlark identifies embedded Starlark execution or configuration failures.
var ErrStarlark = errors.New("starlark execution failed")

var (
// ErrStarlarkInvalidArgument identifies invalid arguments passed to an embedded Starlark builtin.
ErrStarlarkInvalidArgument = errors.New("invalid starlark argument")
// ErrStarlarkTaskTimeout identifies a Starlark task that exceeded its deadline.
ErrStarlarkTaskTimeout = errors.New("starlark task timed out")
// ErrStarlarkProcessFailed identifies a subprocess started by Starlark that failed or never started.
ErrStarlarkProcessFailed = errors.New("starlark process failed")
// ErrStarlarkOutputEncode identifies a top-level Starlark `output` value that cannot be encoded.
ErrStarlarkOutputEncode = errors.New("starlark output encoding failed")
)

const (
// ErrWrapFormat is the standard format string for wrapping errors with context.
// Use with fmt.Errorf to wrap a sentinel error with an underlying error:
Expand Down Expand Up @@ -1822,6 +1836,9 @@ var (
ErrMockFailWithTimesNegative = errors.New("httpmock: FailWithTimes called with negative times; use FailWith for an unlimited failure")
)

// Test step (`type: test`) failure sentinel.
var ErrTestsFailed = errors.New("tests failed")

// ExitCodeError is a typed error that preserves subcommand exit codes.
// This allows the root command to exit with the same code as the subcommand.
// When Code is 0, it indicates successful completion that should exit cleanly without printing errors.
Expand Down
2 changes: 2 additions & 0 deletions examples/scaffolding-yaml-functions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ related_docs:
url: /cli/commands/scaffold/generate
- label: "Validate scaffold templates"
url: /cli/commands/scaffold/validate
- label: "Load external data in scaffolds"
url: /cli/commands/scaffold/generate#loading-external-data-with-include-and-other-yaml-functions
- label: "!include YAML function"
url: /functions/yaml/include
---
Expand Down
43 changes: 16 additions & 27 deletions pkg/asciicast/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
errUtils "github.com/cloudposse/atmos/errors"
iolib "github.com/cloudposse/atmos/pkg/io"
"github.com/cloudposse/atmos/pkg/perf"
"github.com/cloudposse/atmos/pkg/terminal/query"
"github.com/cloudposse/atmos/pkg/ui/theme"
)

Expand Down Expand Up @@ -130,13 +131,15 @@ func newSessionProcessWait(wait func() error) func() error {
}

type sessionState struct {
mu sync.Mutex
output bytes.Buffer
input io.Writer
discard bool
changed chan struct{}
done chan error
cancel context.CancelFunc
mu sync.Mutex
output bytes.Buffer
input io.Writer
// responder answers terminal capability queries (OSC 10/11, CSI 6n) emitted by the recorded shell.
responder *query.Responder
discard bool
changed chan struct{}
done chan error
cancel context.CancelFunc
}

func normalizeSessionOptions(opts *SessionOptions) {
Expand Down Expand Up @@ -227,10 +230,11 @@ func safePTYSize(value int) uint16 {
func newSessionState(ctx context.Context, output io.Reader, input io.Writer, closeOutput func() error) *sessionState {
watchCtx, cancel := context.WithCancel(ctx)
state := &sessionState{
input: input,
changed: make(chan struct{}, 1),
done: make(chan error, 1),
cancel: cancel,
input: input,
responder: query.NewResponder(input),
changed: make(chan struct{}, 1),
done: make(chan error, 1),
cancel: cancel,
}
go state.readOutput(output)
go func() {
Expand Down Expand Up @@ -258,7 +262,7 @@ func (s *sessionState) readOutput(output io.Reader) {

func (s *sessionState) recordOutputChunk(chunk []byte) {
copied := append([]byte(nil), chunk...)
answerTerminalQueries(copied, s.input)
s.responder.Scan(copied)
s.mu.Lock()
discard := s.discard
if !discard {
Expand All @@ -275,21 +279,6 @@ func (s *sessionState) recordOutputChunk(chunk []byte) {
_, _ = iolib.GetContext().Data().Write(copied)
}

func answerTerminalQueries(chunk []byte, input io.Writer) {
if input == nil || len(chunk) == 0 {
return
}
if bytes.Contains(chunk, []byte("\x1b]11;?\x07")) || bytes.Contains(chunk, []byte("\x1b]11;?\x1b\\")) {
_, _ = input.Write([]byte("\x1b]11;rgb:0000/0000/0000\x1b\\"))
}
if bytes.Contains(chunk, []byte("\x1b]10;?\x07")) || bytes.Contains(chunk, []byte("\x1b]10;?\x1b\\")) {
_, _ = input.Write([]byte("\x1b]10;rgb:ffff/ffff/ffff\x1b\\"))
}
for i := 0; i < bytes.Count(chunk, []byte("\x1b[6n")); i++ {
_, _ = input.Write([]byte("\x1b[1;1R"))
}
}

func (s *sessionState) finishRead(err error) {
if isExpectedSessionReadError(err) {
s.done <- nil
Expand Down
19 changes: 0 additions & 19 deletions pkg/asciicast/session_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package asciicast

import (
"bytes"
"context"
"errors"
"io"
Expand Down Expand Up @@ -777,24 +776,6 @@ func TestResetTimerStopsRunningTimer(t *testing.T) {
timer.Stop()
}

func TestAnswerTerminalQueries(t *testing.T) {
var input bytes.Buffer
answerTerminalQueries([]byte("\x1b]11;?\x1b\\\x1b[6n\x1b]10;?\x1b\\\x1b[6n"), &input)

got := input.String()
for _, want := range []string{
"\x1b]11;rgb:0000/0000/0000\x1b\\",
"\x1b]10;rgb:ffff/ffff/ffff\x1b\\",
} {
if !strings.Contains(got, want) {
t.Fatalf("terminal query response missing %q in %q", want, got)
}
}
if count := strings.Count(got, "\x1b[1;1R"); count != 2 {
t.Fatalf("cursor position replies = %d, want 2 in %q", count, got)
}
}

func TestRunSessionExecutesScriptedShellActions(t *testing.T) {
if err := iolib.Initialize(); err != nil {
t.Fatal(err)
Expand Down
Loading
Loading