Skip to content

[v1.0] Enforce a portable process exit-code contract #265

Description

@codeforester

Goal

Make framework results, metadata, JSON envelopes, history, and the actual process agree on exit status.

Background

_normalize_command_result() accepts every Python int, including booleans, negative values, and values above 255:

def _normalize_command_result(result: Any) -> int:
if result is None:
return ExitCode.SUCCESS
if isinstance(result, int):
return result
raise TypeError(f"Commands must return None or an int exit code; got {type(result).__name__}.")
. On a Unix process, returning 300 from run_app() reports 300 in-process, while SystemExit(300) is observed by the parent shell as 44.

This disagreement is unsafe for scripts, schedulers, and cross-language conformance.

Scope

  • Define the portable public exit-code domain.
  • Reject or deterministically normalize invalid command results before metadata and envelopes are finalized.
  • Apply the same rule to native, attached, async, test, and subprocess entry paths.

Acceptance Criteria

  • The documented success/failure/usage/interrupted codes remain stable.
  • bool, negative integers, and out-of-domain integers cannot produce metadata that disagrees with the process.
  • Invalid callback results yield one actionable framework error and a portable failure code.
  • Direct run_app(), testing.invoke(), JSON output, run metadata, history, and real subprocess tests agree.
  • Windows and POSIX expectations are explicit.

Validation

Add boundary cases for -1, 0, 1, 130, 255, 256, 300, True, and non-integers; run the OS matrix.

Non-Goals

Do not redefine consumer-specific meanings for otherwise valid non-zero codes.

Project Fields

  • Status: Backlog
  • Priority: P1
  • Area: CLI
  • Initiative: v1.0 Readiness
  • Size: S

Ownership

Metadata

Metadata

Assignees

Labels

bugSomething is not working

Type

No type

Projects

Status
Backlog

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions