feat: implement deterministic exit codes and structured machine outpu…#173
feat: implement deterministic exit codes and structured machine outpu…#173KhushviB wants to merge 31 commits into
Conversation
…t (A1) - Add --output json/ndjson/table and --quiet flags - Implement deterministic exit codes (0: success, 1: generic, 2: bad args, 3: auth, 4: not found, 5: throttled) - Emit structured JSON errors on stderr during machine modes - Automatically disable ANSI colors and suppress informational output in machine modes
There was a problem hiding this comment.
Pull request overview
Implements a first-cut “machine output contract” for the Cosmos DB Shell by introducing global --output/--quiet flags, adding NDJSON output support, and mapping error conditions to deterministic exit codes intended for CI/CD and automation consumption.
Changes:
- Adds root CLI flags
--output/-oand--quiet/-q, and forcesNoColors+ quiet behavior for machine modes. - Introduces
OutputFormat.Ndjsonplus NDJSON formatting support for JSON-like command results. - Adds exit-code plumbing via
CommandState.ExitCode,ParserErrorCommandState.ExitCode = 2, andErrorCommandState.ExitCodemapping for Cosmos SDK failures and argument errors.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| CosmosDBShell/Program.cs | Adds root options, machine-mode detection, defaulting to NDJSON in non-interactive runs, and maps parse/connect failures to deterministic exit codes. |
| CosmosDBShell/lang/en.ftl | Adds help strings for --output and --quiet. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ShellInterpreter.cs | Implements Quiet suppression for ShellInterpreter.Write* and emits structured JSON errors to stderr in machine modes. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ParserErrorCommandState.cs | Sets parser-error command states to exit code 2. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Core/OutputFormat.cs | Adds Ndjson output format enum value. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ErrorCommandState.cs | Adds exit-code mapping based on exception type / Cosmos status codes. |
| CosmosDBShell/Azure.Data.Cosmos.Shell.Core/CommandState.cs | Adds ExitCode and NDJSON formatting logic; extends SetFormat to accept ndjson. |
Comments suppressed due to low confidence (1)
CosmosDBShell/Azure.Data.Cosmos.Shell.Core/CommandState.cs:70
- For an invalid format string,
SetFormatcurrently throwsShellException, which maps to exit code 1 viaErrorCommandState. Since an invalid--outputvalue is a bad-arguments case, throwing anArgumentExceptionhere will allow it to deterministically map to exit code 2.
{
throw new ShellException(MessageService.GetString("error-invalid_output_format", new Dictionary<string, object> { { "format", outputFormat } }));
}
- Handle root System.CommandLine parser errors via JSON serialization to STDERR in machine modes - Propagate COSMOSDB_SHELL_FORMAT automatically to command formatters - Ignore case on JSON/NDJSON format checks - Bypass interactive RBAC prompts in quiet/machine modes - Prevent trailing blank lines in JSON serialization output - Add integration tests for deterministic machine exit codes - Update CLI argument tables in README
|
Can you fix/comment/close the copilot issues - otherwise we can't merge. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
CosmosDBShell/Program.cs:165
- When
--output json|ndjsonor--quietis used, this early--otelvalidation error still writes plain text to STDOUT (andShellInterpreter.WriteLinecan't honoro.Quietyet becauseShellInterpreter.Instance.Optionsis assigned later). This breaks the machine-output contract by contaminating STDOUT; emit the structured error JSON to STDERR in machine mode.
Environment.ExitCode = 2;
ShellInterpreter.WriteLine(MessageService.GetArgsString(
"otel-error-invalid-endpoint", "endpoint", o.OtlpEndpoint));
return;
}
When --output json or --output ndjson is selected, route AnsiConsole output through TextWriter.Null using a dedicated AnsiConsoleSettings instance. This prevents accidental AnsiConsole writes from commands from polluting machine-readable stdout, while command-state output still uses Console.Out. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- Apply startup machine-mode handling to quiet mode as well as json/ndjson. - Route startup MCP errors through structured stderr output in machine mode instead of unconditional AnsiConsole stdout writes. - Keep interactive behavior unchanged for non-machine runs. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- Treat startup machine mode consistently for quiet/json/ndjson and suppress startup informational stdout (clear-history/theme warnings). - Route startup machine-mode errors through structured stderr JSON and return exit code 2 for invalid --mcp port argument. - In PrintState, bypass AnsiConsole markup rendering in machine mode so JSON/ text outputs always go through Console.Out. - Expand process integration coverage for implicit -c ndjson output, machine- mode clear-history behavior, and invalid --mcp port behavior. - Clarify README machine-mode wording to avoid over-promising stdout guarantees. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
@mkrueger FInally defeated the endboss 🙌🏻 |
|
Ah ok - I enabled auto merge - let's try the slot machine. EDIT: Integration tests are failing :( - unfortunately I need to manually starting it. Running integration tests locally would require the azure cosmos db emulator to run. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Head branch was pushed to by a user without write access
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (2)
CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ShellInterpreter.cs:1917
- Machine-mode error emission here returns early after writing to Console.Error, which bypasses
ErrOutRedirect/AppendErrRedirection. That changes behavior for scripts that redirect stderr (e.g.,2> errors.json) and can break existing redirection semantics in automation.
if (this.Options?.Quiet == true || string.Equals(this.Options?.Output, "json", StringComparison.OrdinalIgnoreCase) || string.Equals(this.Options?.Output, "ndjson", StringComparison.OrdinalIgnoreCase))
{
var errObj = new
{
status = "error",
error = e.Message,
};
Console.Error.WriteLine(JsonSerializer.Serialize(errObj));
return;
}
CosmosDBShell/Azure.Data.Cosmos.Shell.Core/ShellInterpreter.cs:2166
- In machine mode this branch writes parser errors directly to Console.Error and returns, bypassing the shell’s internal stderr redirection (
ErrOutRedirect/2>). That makes redirected error logs incomplete/non-deterministic when running with--quiet/--output json|ndjson.
if ((this.Options?.Quiet == true
|| string.Equals(this.Options?.Output, "json", StringComparison.OrdinalIgnoreCase)
|| string.Equals(this.Options?.Output, "ndjson", StringComparison.OrdinalIgnoreCase))
&& errors != null && errors.Count > 0)
{
var errorStrings = new System.Collections.Generic.List<string>();
foreach (var err in errors)
{
if (err != null && err.ErrorLevel == ErrorLevel.Error)
{
errorStrings.Add(err.Message ?? "Parser error");
}
}
if (errorStrings.Count > 0)
{
var errObj = new
{
status = "error",
error = string.Join("; ", errorStrings),
};
Console.Error.WriteLine(JsonSerializer.Serialize(errObj));
return;
}
| if (inMachineMode) | ||
| { | ||
| AnsiConsole.MarkupLine(Markup.Escape(hinted.Hint)); | ||
| var errObj = new | ||
| { | ||
| status = "error", | ||
| error = e.Message, | ||
| }; | ||
| Console.Error.WriteLine(JsonSerializer.Serialize(errObj)); | ||
| } |
Description (#155 )
This PR implements the deterministic machine output contract (Wave 1, P0) required for the Agentic & Automation Roadmap. It ensures that the Cosmos DB Shell can be safely consumed by downstream CI/CD pipelines, automation tools, and agentic wrappers without output corruption or ambiguous failure states.
Key Changes
--outputand--quietFlags:--output json,ndjson,table, andcsv.--quiet(-q) flag that suppresses all standard informational output (banners, connection logs, etc.).0: Success1: Generic error2: Bad arguments / Parser errors3: Auth / Connection failure4: Not found5: Throttled / RU budget exceeded--output json|ndjsonor--quietare detected, the shell now implicitly disables ANSI color code injections (ColorSystem = NoColors) to prevent STDOUT corruption.--output json|ndjsonis specified, it inherently behaves as--quietto guarantee that no interactive text logs interleave with the JSON objects.stderr:ExitCodeand emitted as a structured JSON object ({"status": "error", "error": "..."}) toSTDERRwhen in machine modes.Validation / Acceptance Criteria Met
-cand-fruns produce parseable NDJSON implicitly.%ERRORLEVEL%/$LASTEXITCODE.