Skip to content

feat(core): bound subprocess and network calls with timeouts (closes #145) - #217

Merged
Dione-b merged 1 commit into
mainfrom
fix/145-subprocess-timeouts
Sep 8, 2026
Merged

feat(core): bound subprocess and network calls with timeouts (closes #145)#217
Dione-b merged 1 commit into
mainfrom
fix/145-subprocess-timeouts

Conversation

@Dione-b

@Dione-b Dione-b commented Sep 8, 2026

Copy link
Copy Markdown
Owner

Motivation

Closes #145.

No subprocess or network call in @caatinga/core had a timeout. A stalled registry, wedged network, or hung stellar process hung the CLI forever — no error, no hint, no recourse but Ctrl-C.

This also matters for #138: the retry loops around deploy/upgrade/post-deploy sit on top of calls that could block indefinitely mid-flight, on an irreversible mainnet operation.

Behavior

runCommand gains an optional timeout, wired into execa. A run killed by it (execa's timedOut) throws CaatingaError(CAATINGA_COMMAND_TIMEOUT) naming the limit that was exceeded. That check runs ahead of the generic failureCode path — otherwise a deploy killed by its timeout would report CAATINGA_DEPLOY_FAILED and bury the actual diagnosis.

Budgets are named constants in packages/core/src/shell/command-timeouts.ts, not a single global default. Caatinga drives commands whose legitimate runtime spans four orders of magnitude, so one default is either tight enough to kill real work or loose enough to catch nothing:

Budget Applies to
30s Version and capability probes — stellar --version, check-binary, stellar keys address, CLI feature probes
60s Registry lookups — npm view @stellar/stellar-sdk version
120s Bindings generation — npx --yes @stellar/stellar-sdk generate (downloads, then generates)
300s Stellar CLI transactions — deploy, upgrade, upload, invoke, read, simulate, post-deploy hooks, contract-id recovery

Deliberately left untimed: contract builds (stellar contract build, cold Cargo compiles) and every circuit/ceremony command in @caatinga/zk. A powers-of-tau ceremony or a cold build legitimately runs for many minutes; bounding those needs a per-project budget, not a constant. This is documented in command-timeouts.ts so the omission reads as a decision rather than an oversight.

New public error code CAATINGA_COMMAND_TIMEOUT, with its row in docs/errors.md.

Tests

  • run-command.test.ts: timeout passes through to execa; stays undefined when not requested; a timedOut rejection maps to COMMAND_TIMEOUT with the limit in the hint; COMMAND_TIMEOUT wins over a configured failureCode.
  • Seven existing tests asserted the exact options object passed to runCommand/execa and were updated to include the new field.
  • error-surface.test.ts covers the new code.

Full run on this branch: core 514/514, cli 175/175, client 94/94, zk 26/26. pnpm -r build, tsc --noEmit and prettier all clean.

Release Impact

Minor. New public error code (additive), new optional RunCommandOptions.timeout, and new exported timeout constants. Behavior change: commands that previously hung now fail after their budget — intended, but worth a release note for anyone whose network path is slower than the version-probe and registry budgets.

Credit

Follows the approach @Galmanus proposed in #183, which was closed unmerged by the author to reduce review load. Extended past the two calls the issue named to every network-facing and transaction call site, plus the COMMAND_TIMEOUT-over-failureCode precedence.

Checklist

  • Public docs updated when behavior changes.
  • CAATINGA_* errors added or updated when public failures change.
  • --source examples use a local Stellar CLI identity, not a public G... address.
  • Version, lockfile, and workflow pins are aligned when tooling changes.

…145)

No subprocess or network call in @caatinga/core had a timeout, so a
stalled registry, wedged network, or hung process hung the CLI forever
with no error and no recourse.

runCommand gains an optional `timeout` wired into execa. A run killed by
it (execa's `timedOut`) throws CaatingaError(CAATINGA_COMMAND_TIMEOUT)
naming the limit that was exceeded, ahead of the generic failureCode
path so the diagnosis is not swallowed.

Budgets live in shell/command-timeouts.ts as named constants rather than
a single global default. Caatinga drives commands whose legitimate
runtime spans four orders of magnitude, so one default is either tight
enough to kill real work or loose enough to catch nothing:

- 30s  version and capability probes (stellar --version, check-binary,
       stellar keys address, feature probes)
- 60s  registry lookups (npm view @stellar/stellar-sdk)
- 120s bindings generation (npx --yes @stellar/stellar-sdk generate)
- 300s Stellar CLI transactions: deploy, upgrade, upload, invoke, read,
       simulate, post-deploy hooks, contract-id recovery

Contract builds and every @caatinga/zk circuit/ceremony command are
deliberately left untimed: a cold Cargo compile or a powers-of-tau
ceremony legitimately runs for many minutes, and bounding those needs a
per-project budget rather than a constant.

The 300s bound on transactions also means a mainnet deploy or upgrade
can no longer hang indefinitely mid-flight, which is the failure mode
the retry loops in #138 would otherwise sit on top of.

Follows the approach proposed by @Galmanus in #183, extended past the
two calls the issue named to every network-facing and transaction call
site, plus the COMMAND_TIMEOUT-over-failureCode precedence.
@Dione-b
Dione-b merged commit 9eb0441 into main Sep 8, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

core: no timeouts on subprocess and network calls (runCommand, npm view, npx)

1 participant