shell

package
v1.6.60 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 20, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package shell provides GitHub CLI wrapper and subprocess helpers.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CheckRateLimit

func CheckRateLimit()

CheckRateLimit checks the GitHub API rate limit and waits if low.

func IsRepoNotFound

func IsRepoNotFound(output string) bool

IsRepoNotFound reports whether a gh repo view failure means the repo doesn't exist (vs a transient failure like network / auth / rate limit). Exported so the runtime preflight can share the same "is this 404 vs transient?" rule without re-implementing string sniffing.

func MustRun

func MustRun(cmdStr, cwd string) string

MustRun executes an external command and aborts the program on failure. Use for external system calls (gh, git, etc.) where partial failure must not be silently swallowed.

func MustRunPostCreate

func MustRunPostCreate(cmdStr, cwd string) string

MustRunPostCreate runs cmdStr with retry on ANY non-rate-limit error using the standard backoff schedule. Use ONLY for `gh` operations that happen immediately after `gh repo create`, where any failure is overwhelmingly likely to be GraphQL/replica index lag rather than a real problem (the repo was just created successfully on the primary). After ~65s of retries the call still aborts — at that point something is genuinely wrong.

Unlike MustRunWithRetry, this does NOT inspect output for transient patterns: vendor error wording can change ("Could not resolve to a Repository" today, something else tomorrow), and in this narrow post-create window a permissive classifier is safe because the only plausible cause is lag we already know about. See waitForRepoVisible in github.go for the related polling mitigation; this helper is the safety net when view-poll and the subsequent operation land on different replicas.

func MustRunPostCreatePush

func MustRunPostCreatePush(cmdStr, cwd string) string

MustRunPostCreatePush runs a `git push` immediately after `gh repo create`, retrying ONLY on the two ref-store replica-lag messages matched by postCreatePushReplicaLag. Both indicate a replica that hasn't converged on main's state yet; a retry against a (possibly different) replica typically lands cleanly. Anything else — non-fast-forward, auth, permission, branch-protection (including `! [remote rejected]` on its own) — is permanent and fails fast.

Use ONLY for the initial template push to a freshly-created repo (commitAndPushRepo with preExisted=false in internal/steps/finalize.go). Subsequent fast-forward pushes against settled repos must use plain shell.Run; the lag class does not apply there.

This deliberately overrides the package-level RetryHardFail rule (which lists `! [remote rejected]` as hard-fail) for the narrow post-create window only. RetryHardFail's classification remains correct for plain MustRunWithRetry callers. Sibling write-side analogue of MustRunPostCreate (read side).

func MustRunStdinWithRetry

func MustRunStdinWithRetry(cmdStr, stdin, cwd string) string

MustRunStdinWithRetry is the retry-wrapped sibling of RunStdin. Aborts on hard-fail or after retries are exhausted. The stdin value never appears in logs, retry chatter, or error messages — safe for secrets.

func MustRunWithRetry

func MustRunWithRetry(cmdStr, cwd string) string

MustRunWithRetry is the retry-wrapped sibling of MustRun. Aborts the program on hard-fail or after retries are exhausted.

func RepoExists

func RepoExists(slug string) (bool, error)

RepoExists reports whether a GitHub repo with the given slug (owner/name) is visible to the authenticated `gh` CLI. Returns (true, nil) on success, (false, nil) when gh reports the repo doesn't exist, and (false, err) for transient failures (network, auth, rate limit) so callers can distinguish "definitely missing" from "couldn't tell."

Shells out the same way init's CreateRepo does — kept in this package (not duplicated in preflight) so there's a single source of truth for the gh-repo existence check and its 404-vs-transient classification.

func RetryHardFail

func RetryHardFail() *regexp.Regexp

RetryHardFail is the unified hard-fail-pattern regex. Exported alongside RetryTransient.

func RetryTransient

func RetryTransient() *regexp.Regexp

RetryTransient is the unified transient-pattern regex. Exported so callers in sibling packages (internal/sonar, internal/config) can plug it into RetryWithPolicy without redefining the pattern.

func RetryWithPolicy

func RetryWithPolicy(
	transient, hardFail *regexp.Regexp,
	prefix string,
	fn func() (string, error),
) (string, error)

RetryWithPolicy is the generic retry engine for tool-specific wrappers. Pass a transient regex, an optional hard-fail regex, a log prefix, and the function to retry. Returns the last attempt's output and error.

Mirrors retry_with_policy in optivem/actions/shared/retry-core.sh so adding a new transient pattern is a one-line edit in one place — not five. Use this for non-gh callers (sonar, docker, future tools). The gh path stays on classifyGHError because rate-limit pass-through requires typed-error inspection (errors.As against RateLimitExceeded), not just regex.

func Run

func Run(cmdStr string, check bool, cwd string) (string, error)

Run executes a shell command. When check=false, non-rate-limit failures are logged (not swallowed silently) but still return a nil error so callers can continue. Rate-limit errors are always returned regardless of check so callers can back off.

func RunCapture

func RunCapture(cmdStr, cwd string) (string, error)

RunCapture runs a command and captures stdout separately. On failure, stderr is captured and included in the returned error so the caller has context.

func RunCaptureWithRetry

func RunCaptureWithRetry(cmdStr, cwd string) (string, error)

RunCaptureWithRetry is the retry-wrapped sibling of RunCapture. Use for `gh` CLI calls whose stdout is parsed (e.g. JSON capture from `gh project list --format json`). Classification still inspects the returned error string, which RunCapture builds from stderr.

func RunPassthrough

func RunPassthrough(cmdStr, cwd string) error

RunPassthrough runs a command with stdout/stderr passed through to the terminal. Output is also captured in-memory and folded into the returned error on failure so the caller's FATAL line is self-contained — the live stream may have scrolled off or been redirected to a log file the user does not look at.

func RunStdin

func RunStdin(cmdStr, stdin string, check bool, cwd string) (string, error)

RunStdin is like Run but pipes stdin to the process. Use when an argument would contain a secret (token, password) that must not appear in the command line — passing it via stdin keeps it out of error messages, retry logging, and process lists (argv is readable by other local users on most systems, and any error surfaces the full cmdStr). cmdStr is the command as it should appear in logs; it never includes the stdin value.

func RunWithRetry

func RunWithRetry(cmdStr string, check bool, cwd string) (string, error)

RunWithRetry is the retry-wrapped sibling of Run. Use for `gh` CLI calls that talk to the GitHub API. Git calls and other local commands should use plain Run — retrying a local git operation rarely helps and can mask bugs.

func SetRunCaptureFnForTest

func SetRunCaptureFnForTest(fn func(string, string) (string, error)) (restore func())

SetRunCaptureFnForTest swaps the package-level runCaptureFn (the function RunCaptureWithRetry shells out to) so tests can exercise call sites like RunWatchWorkflow's appear-poll loop without a real `gh` binary.

func SetRunFnForTest

func SetRunFnForTest(fn func(string, bool, string) (string, error)) (restore func())

SetRunFnForTest swaps the package-level runFn (the function RunWithRetry shells out to) so tests can exercise call sites like RepoExists, waitForRepoVisible, and pollRunUntilComplete without invoking a real `gh` binary. Returns a restore func to be deferred.

func SetSleepFnForTest

func SetSleepFnForTest(fn func(time.Duration)) (restore func())

SetSleepFnForTest swaps the package-level sleepFn so tests in sibling packages (e.g. internal/sonar) can stub out the 5s/15s/45s backoff and finish in milliseconds instead of seconds. Returns a restore func to be deferred. NOT for production use — the only reason it's exported is cross-package retry-loop tests can't reach the unexported sleepFn directly.

func SetSleepForTest added in v1.6.34

func SetSleepForTest(fn func(time.Duration)) func()

SetSleepForTest replaces the inter-attempt backoff sleep with fn and returns a restore function. Test-only: lets callers in OTHER packages (e.g. clauderun, whose Dispatch routes through RetryWithPolicy) no-op the backoff so retry-path tests don't sleep for real seconds. Mirrors the unexported sleepFn/nowFn seam convention used elsewhere in the codebase; kept minimal and clearly test-only. Restore with the returned func in a defer.

Types

type GitHub

type GitHub struct {
	Repo string
}

GitHub wraps gh CLI calls for a specific repo.

func NewGitHub

func NewGitHub(fullRepo string) *GitHub

func (*GitHub) Clone

func (g *GitHub) Clone(dest string)

func (*GitHub) CreateEnvironment

func (g *GitHub) CreateEnvironment(name string)

func (*GitHub) CreateRepo

func (g *GitHub) CreateRepo()

func (*GitHub) Delete

func (g *GitHub) Delete()

Delete is best-effort cleanup; teardown happens after the main work has either succeeded or already failed, so we log failures but don't abort.

func (*GitHub) ForRepo

func (g *GitHub) ForRepo(fullRepo string) *GitHub

func (*GitHub) LabelCreate

func (g *GitHub) LabelCreate(name, color, description string)

LabelCreate creates (or updates) a repo label. `--force` makes the call idempotent: re-running updates color/description without erroring on existing labels.

func (*GitHub) RunWatchPushWorkflow added in v1.6.35

func (g *GitHub) RunWatchPushWorkflow(workflow string, intervalSecs int) error

RunWatchPushWorkflow watches a workflow whose run is triggered by a push (e.g. the commit-stage workflows fired by the scaffold's initial push), recovering from GitHub's first-push trigger drop: on a freshly created repo's first push the Actions subsystem can fail to build the run graph — emitting a startup_failure or simply dropping the trigger silently — so the expected push-triggered run never appears.

When the run does not appear within the appear-window, this re-fires the workflow via workflow_dispatch, bounded to maxReDispatches. The on.push paths: filter is validated statically before push (VerifyPushPathsFilter), so re-dispatching unconditionally here is safe: a bad filter would have been caught earlier.

Actions-side analogue of MustRunPostCreatePush, which retries the ref-store replica lag seen on the same fresh-repo first-push window.

func (*GitHub) RunWatchWorkflow

func (g *GitHub) RunWatchWorkflow(workflow string, intervalSecs int) error

RunWatchWorkflow watches the latest run for a specific workflow name. If workflow is empty, watches the overall latest run. Waits up to (runAppearAttempts × runAppearPollSecs) for the run to appear. intervalSecs controls the polling frequency for gh run watch. If gh run watch hits a rate limit mid-stream, falls back to manual polling.

func (*GitHub) SecretSet

func (g *GitHub) SecretSet(name, value string)

func (*GitHub) VariableSet

func (g *GitHub) VariableSet(name, value string)

func (*GitHub) WorkflowRun

func (g *GitHub) WorkflowRun(workflow string, fields map[string]string)

type RateLimitExceeded

type RateLimitExceeded struct {
	Msg string
}

RateLimitExceeded is returned when a gh command fails due to rate limiting.

func (*RateLimitExceeded) Error

func (e *RateLimitExceeded) Error() string

type SonarCloud

type SonarCloud struct {
	Token string
	Org   string
	// contains filtered or unexported fields
}

SonarCloud wraps SonarCloud API calls.

func NewSonarCloud

func NewSonarCloud(token, org string) *SonarCloud

func (*SonarCloud) CreateOrg

func (s *SonarCloud) CreateOrg()

func (*SonarCloud) CreateProject

func (s *SonarCloud) CreateProject(key string)

func (*SonarCloud) DeleteProject

func (s *SonarCloud) DeleteProject(key string)

func (*SonarCloud) OrgExists

func (s *SonarCloud) OrgExists(ctx context.Context, key string) (bool, error)

OrgExists reports whether a SonarCloud organization with the given key is visible to the authenticated client. Returns (true, nil) when the org is found, (false, nil) when the search returns no matches, and (false, err) on transport / authentication / unexpected-status failures.

Used by the runtime preflight to surface "the SonarCloud org you declared in gh-optivem.yaml doesn't exist" before any agent dispatch touches a runner that requires it.

func (*SonarCloud) ProjectExists

func (s *SonarCloud) ProjectExists(ctx context.Context, key string) (bool, error)

ProjectExists reports whether a SonarCloud project with the given key is visible to the authenticated client. Uses /components/show because it returns a clean 404 for missing keys (no need to scan a search result for the exact match). Returns (true, nil) on 200, (false, nil) on 404, (false, err) on every other status or transport failure.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL