Documentation
¶
Overview ¶
Package shell provides GitHub CLI wrapper and subprocess helpers.
Index ¶
- func CheckRateLimit()
- func IsRepoNotFound(output string) bool
- func MustRun(cmdStr, cwd string) string
- func MustRunPostCreate(cmdStr, cwd string) string
- func MustRunPostCreatePush(cmdStr, cwd string) string
- func MustRunStdinWithRetry(cmdStr, stdin, cwd string) string
- func MustRunWithRetry(cmdStr, cwd string) string
- func RepoExists(slug string) (bool, error)
- func RetryHardFail() *regexp.Regexp
- func RetryTransient() *regexp.Regexp
- func RetryWithPolicy(transient, hardFail *regexp.Regexp, prefix string, fn func() (string, error)) (string, error)
- func Run(cmdStr string, check bool, cwd string) (string, error)
- func RunCapture(cmdStr, cwd string) (string, error)
- func RunCaptureWithRetry(cmdStr, cwd string) (string, error)
- func RunPassthrough(cmdStr, cwd string) error
- func RunStdin(cmdStr, stdin string, check bool, cwd string) (string, error)
- func RunWithRetry(cmdStr string, check bool, cwd string) (string, error)
- func SetRunCaptureFnForTest(fn func(string, string) (string, error)) (restore func())
- func SetRunFnForTest(fn func(string, bool, string) (string, error)) (restore func())
- func SetSleepFnForTest(fn func(time.Duration)) (restore func())
- func SetSleepForTest(fn func(time.Duration)) func()
- type GitHub
- func (g *GitHub) Clone(dest string)
- func (g *GitHub) CreateEnvironment(name string)
- func (g *GitHub) CreateRepo()
- func (g *GitHub) Delete()
- func (g *GitHub) ForRepo(fullRepo string) *GitHub
- func (g *GitHub) LabelCreate(name, color, description string)
- func (g *GitHub) RunWatchPushWorkflow(workflow string, intervalSecs int) error
- func (g *GitHub) RunWatchWorkflow(workflow string, intervalSecs int) error
- func (g *GitHub) SecretSet(name, value string)
- func (g *GitHub) VariableSet(name, value string)
- func (g *GitHub) WorkflowRun(workflow string, fields map[string]string)
- type RateLimitExceeded
- type SonarCloud
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
MustRunWithRetry is the retry-wrapped sibling of MustRun. Aborts the program on hard-fail or after retries are exhausted.
func RepoExists ¶
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 ¶
RetryHardFail is the unified hard-fail-pattern regex. Exported alongside RetryTransient.
func RetryTransient ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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
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 (*GitHub) CreateEnvironment ¶
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) LabelCreate ¶
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
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 ¶
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) VariableSet ¶
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 ¶
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 ¶
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 ¶
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.