openclaw - ✅(Solved) Fix [Bug]: `openclaw crestodian` exits 0 with "needs interactive TTY" error in non-TTY contexts; sibling subcommands correctly exit non-zero on the same input [4 pull requests, 2 comments, 3 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#73646Fetched 2026-04-29 06:17:00
View on GitHub
Comments
2
Participants
3
Timeline
8
Reactions
0
Author
Timeline (top)
cross-referenced ×4commented ×2labeled ×2

pnpm openclaw crestodian (and equivalently node ./dist/index.js crestodian) detects when stdin is not a TTY, prints Crestodian needs an interactive TTY. Use --message for one command. to stdout, then exits with code 0. Shell scripts and CI flows reading the exit code see "success" and proceed as if Crestodian ran. The two in-tree precedents for the same condition (no TTY) both exit non-zero: models auth login exits 1 with Error: models auth login requires an interactive TTY., and plugins uninstall < /dev/null exits 13 (separate filed bug #73562 — wrong code, but at least non-zero). Crestodian is the only one of the three that returns success despite the error message.

Error Message

=== The bug: crestodian + no TTY → error message + exit 0 (success) ===

$ pnpm openclaw crestodian < /dev/null Crestodian needs an interactive TTY. Use --message for one command. $ echo $? 0

=== Reproduces via direct binary too — not a pnpm wrapper artifact ===

$ node ./dist/index.js crestodian < /dev/null Crestodian needs an interactive TTY. Use --message for one command. $ echo $? 0

$ node ./scripts/run-node.mjs crestodian < /dev/null Crestodian needs an interactive TTY. Use --message for one command. $ echo $? 0

=== In-tree counter-examples (same TTY-required condition, correct exit codes) ===

$ echo "n" | pnpm openclaw models auth login --provider deepseek Error: models auth login requires an interactive TTY. ELIFECYCLE Command failed with exit code 1. $ echo $? 1

$ pnpm openclaw plugins uninstall <some-id> < /dev/null ... crashes with "Detected unsettled top-level await ... ELIFECYCLE Command failed with exit code 13." (filed as #73562 — wrong code, but at least non-zero)

=== Bare-invocation behavior (different code path, exit code differs by wrapper) ===

$ pnpm --silent openclaw < /dev/null Crestodian needs an interactive TTY. Use openclaw crestodian --message "status" for one command. $ echo $? 1

$ node ./dist/index.js < /dev/null Crestodian needs an interactive TTY. Use openclaw crestodian --message "status" for one command. $ echo $? 0

(The bare-invocation fall-through to crestodian exits 0 via direct binary too. The pnpm-wrapped variant exits 1 only because pnpm injects exit 1 when no script content executes. The underlying CLI exit is 0 in both cases for both bare and explicit invocation.)

Root Cause

(The bare-invocation fall-through to crestodian exits 0 via direct binary too. The pnpm-wrapped variant exits 1 only because pnpm injects exit 1 when no script content executes. The underlying CLI exit is 0 in both cases for both bare and explicit invocation.)

Fix Action

Fix / Workaround

The error string is correct and points the user at the --message workaround. Only the exit code is wrong — it should be non-zero so shell scripts can detect the failure.

PR fix notes

PR #73654: fix(cli): make crestodian exit non-zero on no-TTY (#73646)

Description (problem / solution / changelog)

What

Fixes #73646. pnpm openclaw crestodian < /dev/null (and node ./dist/index.js crestodian < /dev/null) currently:

  1. Detects no TTY
  2. Prints Crestodian needs an interactive TTY. Use --message for one command. to stderr
  3. Returns from runCrestodian cleanly — no thrown error, no process.exitCode set
  4. The CLI wrapper runCommandWithRuntime only sets runtime.exit(1) in its catch path, so the silent return falls through and Node exits 0

Shell scripts and CI flows reading $? see success and proceed as if Crestodian ran. The two in-tree sibling subcommands for the same condition (models auth login, secrets configure) both throw new Error("…") and exit non-zero. The bare-root crestodian path at cli/run-main.ts:399-404 also already sets process.exitCode = 1 directly. Crestodian-as-subcommand is the lone outlier.

Fix

Replace the silent runtime.error(...) + return in src/crestodian/crestodian.ts:93-96 with a throw new Error(...). The CLI wrapper's catch then runs runtime.error + exit(1) and the process exits non-zero. Behavior matches the bare-root path and the models auth login / secrets configure precedents.

if (!interactive || !inputIsTty || !outputIsTty) {
  throw new Error("Crestodian needs an interactive TTY. Use --message for one command.");
}

Verified locally

npx oxlint src/crestodian/crestodian.ts src/crestodian/crestodian.test.ts
# Found 0 warnings and 0 errors.

npx vitest run src/crestodian/crestodian.test.ts
# Tests  4 passed (4)

The new test pins the regression: runCrestodian({ input: { isTTY: false }, output: { isTTY: false } }) rejects with /needs an interactive TTY/ so the CLI wrapper's catch fires.

Pre-implement audit

  1. Existing-helper check. Error message + pattern reused verbatim from sibling subcommands (models auth login, secrets configure); setup-token uses the identical throw new Error("… requires an interactive TTY.") shape. ✅
  2. Shared-helper caller check. runCrestodian is called from 4 sites — cli/run-main.ts:423,433, cli/program/register.onboard.ts:182, cli/program/register.crestodian.ts:30 — all wrapped in runCommandWithRuntime (or runCommandWithRuntime-style catch in run-main.ts). All four catch paths surface the thrown error and exit non-zero. ✅
  3. Broader-fix rival scan. Zero rival PRs reference #73646. ✅

Note

#73562 (plugins uninstall < /dev/null exits 13) is a separate bug filed by the same reporter — wrong exit code there, but at least non-zero. Not addressed here.

lobster-biscuit: 73646-crestodian-no-tty-exit

Sign-Off:

  • I have read and agree to the OpenClaw Contributor License Agreement.

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • src/crestodian/crestodian.test.ts (modified, +21/-0)
  • src/crestodian/crestodian.ts (modified, +6/-2)

PR #73776: fix(cli): make crestodian exit non-zero on no-TTY (#73646)

Description (problem / solution / changelog)

Fixes #73646.

Re-submitting the closed PR #73654 (auto-closed during overnight queue rotation) with no functional change. crestodian invoked without a TTY now exits non-zero with a clear stderr message instead of the silent zero-exit that broke CI scripts.

What changed

  • `src/crestodian/crestodian.ts` — early-return with non-zero exit + stderr line when stdout is not a TTY
  • `src/crestodian/crestodian.test.ts` — coverage for the no-TTY path

Test

``` pnpm vitest run src/crestodian/crestodian.test.ts ```

🦞 lobster-biscuit


Sign-Off: hclsys

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • src/crestodian/crestodian.test.ts (modified, +21/-0)
  • src/crestodian/crestodian.ts (modified, +6/-2)

PR #73928: fix(cli): set non-zero exit code when crestodian detects non-TTY [AI-assisted]

Description (problem / solution / changelog)

🤖 AI-assisted (built with Codex via Hermes orchestration). Test level: fully tested. Prompt summary available on request.

Summary

  • Problem: runCrestodian() detects a non-TTY stdin/stdout and prints an error message, but returns without setting process.exitCode. The process exits with code 0, misleading shell scripts and CI flows into thinking Crestodian ran successfully.
  • Why it matters: Exit code 0 signals success. Scripts gating on Crestodian's exit code silently proceed despite the error. The two sibling subcommands (models auth login and plugins uninstall) both exit non-zero for the same condition.
  • What changed: Added process.exitCode = 1 before the early return in runCrestodian() (consistent with the parallel TTY guard already present in run-main.ts). Added two regression tests covering the stdin-not-TTY and stdout-not-TTY paths.
  • What did NOT change (scope boundary): No changes to the interactive TUI path, the one-shot --message path, or the run-main.ts bare-root TTY guard.

Change Type (select all)

  • Bug fix

Scope (select all touched areas)

  • CLI

Linked Issue/PR

  • Closes #73646
  • This PR fixes a bug or regression

Root Cause

  • Root cause: The runCrestodian() function in src/crestodian/crestodian.ts called runtime.error() and returned early when stdin/stdout was not a TTY, but never set process.exitCode. The parallel guard in src/cli/run-main.ts (line 403) already sets process.exitCode = 1 for the same condition on the bare-root path, but the explicit openclaw crestodian subcommand path was missed.
  • Missing detection / guardrail: No test asserted process.exitCode after the non-TTY early return.
  • Contributing context: The two code paths (bare root in run-main.ts vs explicit subcommand in crestodian.ts) were likely written at different times, and the exit code was only added to the former.

Regression Test Plan

  • Coverage level that should have caught this:
    • Unit test
  • Target test or file: src/crestodian/crestodian.test.ts
  • Scenario the test should lock in: When runCrestodian() is called with input.isTTY = false or output.isTTY = false, process.exitCode must be set to 1.
  • Why this is the smallest reliable guardrail: The TTY check is purely a process-level concern — a unit test asserting process.exitCode after calling runCrestodian() with mock streams covers the exact failure mode without needing integration infrastructure.
  • Existing test that already covers this (if any): N/A
  • If no new test is added, why not: N/A — two new tests added.

User-visible / Behavior Changes

  • openclaw crestodian (and bare openclaw on the explicit subcommand path) now exits with code 1 instead of 0 when stdin or stdout is not a TTY. The error message is unchanged.

Diagram (if applicable)

N/A

Security Impact (required)

  • New permissions/capabilities? No
  • This fix only changes the process exit code from 0 to 1 in an error path. No security-sensitive behavior is affected.

Changed files

  • src/crestodian/crestodian.test.ts (modified, +34/-1)
  • src/crestodian/crestodian.ts (modified, +2/-1)

PR #74059: fix(crestodian): exit with code 1 on non-TTY stdin

Description (problem / solution / changelog)

Summary

Fixes #73646

The crestodian command now exits with code 1 when stdin is not a TTY, matching sibling commands like models auth login and secrets configure.

Problem

Previously, openclaw crestodian < /dev/null would:

  • Print error message: "Crestodian needs an interactive TTY. Use --message for one command."
  • Exit with code 0 (success)
  • Shell scripts and CI would incorrectly see "success" and proceed

Solution

Added runtime.exit(1); before the return; statement when TTY check fails.

Changes

if (!interactive || !inputIsTty || !outputIsTty) {
    runtime.error("Crestodian needs an interactive TTY. Use --message for one command.");
+   runtime.exit(1);
    return;
}

Test Plan

Added 3 regression tests:

  1. stdin not TTY: Simulates piping from /dev/null
  2. stdout not TTY: Simulates output redirect
  3. interactive flag false: Explicit non-interactive mode

All tests verify:

  • exit 1 is thrown
  • Error message is printed

Manual Testing

# Before fix
node dist/index.js crestodian < /dev/null
echo $?  # 0 (wrong)

# After fix
node dist/index.js crestodian < /dev/null
echo $?  # 1 (correct)

Workaround

Users can still use --message for one-shot commands:

openclaw crestodian -m "status"

Security

No security implications - purely exit code fix for shell script compatibility.

Changed files

  • src/crestodian/crestodian.test.ts (modified, +52/-0)
  • src/crestodian/crestodian.ts (modified, +1/-0)

Code Example

$ pnpm openclaw crestodian < /dev/null
Crestodian needs an interactive TTY. Use --message for one command.
$ echo $?
0

$ node ./dist/index.js crestodian < /dev/null
Crestodian needs an interactive TTY. Use --message for one command.
$ echo $?
0

$ node ./scripts/run-node.mjs crestodian < /dev/null
Crestodian needs an interactive TTY. Use --message for one command.
$ echo $?
0

---

=== The bug: crestodian + no TTY → error message + exit 0 (success) ===

$ pnpm openclaw crestodian < /dev/null
Crestodian needs an interactive TTY. Use --message for one command.
$ echo $?
0

=== Reproduces via direct binary too — not a pnpm wrapper artifact ===

$ node ./dist/index.js crestodian < /dev/null
Crestodian needs an interactive TTY. Use --message for one command.
$ echo $?
0

$ node ./scripts/run-node.mjs crestodian < /dev/null
Crestodian needs an interactive TTY. Use --message for one command.
$ echo $?
0

=== In-tree counter-examples (same TTY-required condition, correct exit codes) ===

$ echo "n" | pnpm openclaw models auth login --provider deepseek
Error: models auth login requires an interactive TTY.
 ELIFECYCLE  Command failed with exit code 1.
$ echo $?
1

$ pnpm openclaw plugins uninstall <some-id> < /dev/null
... crashes with "Detected unsettled top-level await ... ELIFECYCLE Command failed with exit code 13."
(filed as #73562 — wrong code, but at least non-zero)

=== Bare-invocation behavior (different code path, exit code differs by wrapper) ===

$ pnpm --silent openclaw < /dev/null
Crestodian needs an interactive TTY. Use `openclaw crestodian --message "status"` for one command.
$ echo $?
1

$ node ./dist/index.js < /dev/null
Crestodian needs an interactive TTY. Use `openclaw crestodian --message "status"` for one command.
$ echo $?
0

(The bare-invocation fall-through to crestodian exits 0 via direct binary too. The pnpm-wrapped variant exits 1 only because pnpm injects exit 1 when no script content executes. The underlying CLI exit is 0 in both cases for both bare and explicit invocation.)
RAW_BUFFERClick to expand / collapse

Bug type

Behavior bug (incorrect output/state without crash)

Beta release blocker

No

Summary

pnpm openclaw crestodian (and equivalently node ./dist/index.js crestodian) detects when stdin is not a TTY, prints Crestodian needs an interactive TTY. Use --message for one command. to stdout, then exits with code 0. Shell scripts and CI flows reading the exit code see "success" and proceed as if Crestodian ran. The two in-tree precedents for the same condition (no TTY) both exit non-zero: models auth login exits 1 with Error: models auth login requires an interactive TTY., and plugins uninstall < /dev/null exits 13 (separate filed bug #73562 — wrong code, but at least non-zero). Crestodian is the only one of the three that returns success despite the error message.

Steps to reproduce

  1. Fresh checkout of openclaw at v2026.4.27 (commit 0450bba); pnpm install && pnpm build on Node 22.22.2.
  2. Run pnpm openclaw crestodian < /dev/null. Observe Crestodian needs an interactive TTY. Use --message for one command. on stdout. Run echo $? — exit code is 0.
  3. Run via direct binary to rule out pnpm: node ./dist/index.js crestodian < /dev/null. Same output, exit 0.
  4. For comparison, sibling subcommand: echo "n" | pnpm openclaw models auth login --provider deepseek. Observe Error: models auth login requires an interactive TTY., exit 1.
  5. Or: pnpm openclaw plugins uninstall whatsapp < /dev/null (filed as #73562). Observe crash with exit 13. Wrong code but at least non-zero — Crestodian is the outlier returning success.

Expected behavior

When stdin is not a TTY and there is no --message argument to short-circuit the prompt, crestodian should print the same error and exit non-zero (typically 1, matching models auth login's pattern). Shell scripts and CI checks must see a non-zero exit so the failure propagates.

Concrete grounded references in the same CLI/build:

  • pnpm openclaw models auth login on no-TTY stdin → Error: models auth login requires an interactive TTY., exit 1.
  • pnpm openclaw plugins uninstall <id> < /dev/null (filed bug #73562) → crash with exit 13. Both are non-zero. Crestodian's exit 0 is the outlier.

Actual behavior

$ pnpm openclaw crestodian < /dev/null
Crestodian needs an interactive TTY. Use --message for one command.
$ echo $?
0

$ node ./dist/index.js crestodian < /dev/null
Crestodian needs an interactive TTY. Use --message for one command.
$ echo $?
0

$ node ./scripts/run-node.mjs crestodian < /dev/null
Crestodian needs an interactive TTY. Use --message for one command.
$ echo $?
0

The error string is correct and points the user at the --message workaround. Only the exit code is wrong — it should be non-zero so shell scripts can detect the failure.

OpenClaw version

2026.4.27 (commit 0450bba)

Operating system

Ubuntu 24.04.4 LTS (Linux 6.8.0-110-generic)

Install method

pnpm dev

Model

anthropic/claude-opus-4-7

Provider / routing chain

N/A

Additional provider/model setup details

N/A

Logs, screenshots, and evidence

=== The bug: crestodian + no TTY → error message + exit 0 (success) ===

$ pnpm openclaw crestodian < /dev/null
Crestodian needs an interactive TTY. Use --message for one command.
$ echo $?
0

=== Reproduces via direct binary too — not a pnpm wrapper artifact ===

$ node ./dist/index.js crestodian < /dev/null
Crestodian needs an interactive TTY. Use --message for one command.
$ echo $?
0

$ node ./scripts/run-node.mjs crestodian < /dev/null
Crestodian needs an interactive TTY. Use --message for one command.
$ echo $?
0

=== In-tree counter-examples (same TTY-required condition, correct exit codes) ===

$ echo "n" | pnpm openclaw models auth login --provider deepseek
Error: models auth login requires an interactive TTY.
 ELIFECYCLE  Command failed with exit code 1.
$ echo $?
1

$ pnpm openclaw plugins uninstall <some-id> < /dev/null
... crashes with "Detected unsettled top-level await ... ELIFECYCLE Command failed with exit code 13."
(filed as #73562 — wrong code, but at least non-zero)

=== Bare-invocation behavior (different code path, exit code differs by wrapper) ===

$ pnpm --silent openclaw < /dev/null
Crestodian needs an interactive TTY. Use `openclaw crestodian --message "status"` for one command.
$ echo $?
1

$ node ./dist/index.js < /dev/null
Crestodian needs an interactive TTY. Use `openclaw crestodian --message "status"` for one command.
$ echo $?
0

(The bare-invocation fall-through to crestodian exits 0 via direct binary too. The pnpm-wrapped variant exits 1 only because pnpm injects exit 1 when no script content executes. The underlying CLI exit is 0 in both cases for both bare and explicit invocation.)

Impact and severity

Affected users/systems/channels:

  • Every operator/CI flow that explicitly invokes openclaw crestodian from a non-interactive shell. Linux directly observed (Ubuntu 24.04 / Node 22.22.2 / pnpm 10.33.0); platform-agnostic code path so macOS/Windows are expected to reproduce, only Linux directly verified.
  • Provisioning scripts that include openclaw crestodian as a setup step and check the exit code to gate downstream actions. Today those scripts see exit 0 and proceed as if Crestodian ran successfully.

Severity:

  • Annoying with real CI/automation impact. Exit 0 on a "needs TTY" error is a silent failure — the operator has no way to detect the failure from the exit code alone.
  • Inconsistent with the in-tree pattern. models auth login and plugins uninstall both exit non-zero on the same condition. Crestodian is the only TTY-required subcommand that lies about success.
  • Not a security issue, no data corruption, no crash.

Frequency:

  • Always, deterministic. 100% reproduction across pnpm openclaw crestodian, node ./dist/index.js crestodian, and node ./scripts/run-node.mjs crestodian when stdin is not a TTY.

Consequence:

  • CI/automation that runs openclaw crestodian as part of a setup pipeline will silently skip the actual setup work and continue to downstream steps that assume crestodian ran.
  • Operators debugging "why didn't crestodian run?" will not find a non-zero exit to point at; they have to read the stdout text.
  • Inconsistency across TTY-required subcommands erodes operator trust in CLI exit codes generally.
  • No grounded evidence of missed messages, failed onboarding, or extra cost.

Additional information

  • Regression status: not classified as a Regression. Last-known-good not directly observed; no bisect performed.

  • Likely fix locus: the action handler for crestodian (commander definition wherever the parser lives in this build — search for "crestodian" or "Crestodian needs an interactive TTY"). Two viable approaches:

    1. Match models auth login's pattern — emit the error and process.exit(1) (or process.exitCode = 1; throw) explicitly when no TTY is detected. Minimal change, matches existing convention.
    2. Move the TTY check into a shared helper used by all interactive subcommands (models auth login, plugins uninstall, crestodian, possibly setup/onboard/configure) so the no-TTY behavior is consistent across the surface. Approach (1) is one-line; (2) is the better long-term fix and would also normalize the plugins uninstall exit-code issue (#73562).
  • Suggested regression test: a unit test on the crestodian action that mocks non-TTY stdin and asserts (a) exit code is non-zero (1, matching siblings), (b) the error message appears, (c) the interactive setup flow is not invoked. Pair with a parity test asserting models auth login, plugins uninstall, and crestodian produce equivalent exit-code behavior on the same no-TTY input shape (modulo error wording).

  • Related findings from the same session:

    • #73562 (filed): plugins uninstall < /dev/null crashes with exit 13. Different code path (the prompt's await never settles), same theme of "TTY-required subcommand misbehaves under non-TTY stdin." Could be addressed together if (2) above is the chosen fix.
    • #73185 (filed): empty --prompt reaches the provider on --local transport. Unrelated surface but same theme of "documented contract differs from actual behavior."
    • Pattern note: at least three TTY-required subcommands (models auth login, plugins uninstall, crestodian) handle no-TTY in three different ways — exit 1 with clear error, exit 13 via crash, exit 0 with error string. A unified TTY-check helper would normalize all three.
  • Dedupe checked against the openclaw issue corpus on 2026-04-28: no existing open or closed issue matches the "crestodian exits 0 on no-TTY" pattern. Searched for "crestodian", "Crestodian needs an interactive TTY", "openclaw bare command crestodian", "no command default crestodian" — zero direct matches.

  • Not exercised in this repro: behavior with --message argument (Crestodian's documented short-circuit for non-interactive runs); macOS/Windows behavior; the setup/onboard/configure subcommands which also appear interactive but were not directly tested for the exit-code shape.

extent analysis

TL;DR

The crestodian command should exit with a non-zero code when stdin is not a TTY, instead of printing an error message and exiting with code 0.

Guidance

  • Review the action handler for crestodian to ensure it exits with a non-zero code when no TTY is detected, matching the pattern of models auth login.
  • Consider moving the TTY check into a shared helper used by all interactive subcommands to ensure consistent behavior.
  • Add a unit test to verify the crestodian action exits with a non-zero code and prints the correct error message when stdin is not a TTY.
  • Check for similar issues in other TTY-required subcommands, such as plugins uninstall, and consider addressing them together.

Example

// Example of how to exit with a non-zero code when no TTY is detected
if (!process.stdout.isTTY) {
  console.error('Crestodian needs an interactive TTY. Use --message for one command.');
  process.exit(1);
}

Notes

  • The fix should be applied to the crestodian action handler, and potentially to other TTY-required subcommands.
  • A shared helper for TTY checks could simplify the code and ensure consistency across subcommands.
  • Additional testing is needed to verify the fix and ensure similar issues are not present in other parts of the codebase.

Recommendation

Apply a workaround by modifying the crestodian action handler to exit with a non-zero code when no TTY is detected, as shown in the example above. This will ensure consistent behavior with other TTY-required subcommands and prevent silent failures in CI/automation scripts.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

When stdin is not a TTY and there is no --message argument to short-circuit the prompt, crestodian should print the same error and exit non-zero (typically 1, matching models auth login's pattern). Shell scripts and CI checks must see a non-zero exit so the failure propagates.

Concrete grounded references in the same CLI/build:

  • pnpm openclaw models auth login on no-TTY stdin → Error: models auth login requires an interactive TTY., exit 1.
  • pnpm openclaw plugins uninstall <id> < /dev/null (filed bug #73562) → crash with exit 13. Both are non-zero. Crestodian's exit 0 is the outlier.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING