Skip to content

Commit be07ad7

Browse files
committed
feat: add --exit-code-on-api-error flag + Buildkite-aware infra error logging
Adds a configurable exit code for API/infrastructure failures so CI pipelines can distinguish them from blocking security findings (exit 1), without changing any default behavior. - New CliConfig field exit_code_on_api_error (default 3) + --exit-code-on-api-error flag. The CLI already exited 3 on unexpected errors; this just makes that code configurable (e.g. remap to a Buildkite soft_fail code, or 0 to swallow). - New _emit_infrastructure_error helper + IS_BUILDKITE gate: emits Buildkite log section markers (^^^ +++ / --- ⚠️) and a soft_fail hint when running in Buildkite; plain log.error elsewhere so markers don't leak as literal text. - Wire the top-level generic-exception handler in cli() through the helper and the configurable code. Deliberately NON-breaking for 2.3.x: - --disable-blocking STILL forces exit 0 for all outcomes and takes precedence over --exit-code-on-api-error (documented in the flag help so the two aren't combined by mistake). - Default exit codes are unchanged; the exit code only changes when the user explicitly passes the flag. The breaking variant (infra errors bypassing --disable-blocking, distinct RequestTimeoutExceeded handling, exit 1 -> 3 for diff API failures) is intentionally deferred to a future 3.0 release. Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>
1 parent d502ab3 commit be07ad7

2 files changed

Lines changed: 57 additions & 4 deletions

File tree

socketsecurity/config.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ class CliConfig:
101101
pending_head: bool = False
102102
enable_diff: bool = False
103103
timeout: Optional[int] = 1200
104+
exit_code_on_api_error: int = 3
104105
exclude_license_details: bool = False
105106
include_module_folders: bool = False
106107
repo_is_public: bool = False
@@ -219,6 +220,7 @@ def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig':
219220
'integration_type': args.integration,
220221
'pending_head': args.pending_head,
221222
'timeout': args.timeout,
223+
'exit_code_on_api_error': args.exit_code_on_api_error,
222224
'exclude_license_details': args.exclude_license_details,
223225
'include_module_folders': args.include_module_folders,
224226
'repo_is_public': args.repo_is_public,
@@ -802,6 +804,21 @@ def create_argument_parser() -> argparse.ArgumentParser:
802804
help="Timeout in seconds for API requests",
803805
required=False
804806
)
807+
advanced_group.add_argument(
808+
"--exit-code-on-api-error",
809+
dest="exit_code_on_api_error",
810+
type=int,
811+
default=3,
812+
metavar="<int>",
813+
help=(
814+
"Exit code to use when the CLI fails on an API or infrastructure error "
815+
"(timeout, network failure, unexpected exception). Default: 3. Useful for "
816+
"distinguishing infrastructure failures from security findings (exit 1) in "
817+
"CI -- e.g. set to a Buildkite soft_fail code. NOTE: --disable-blocking "
818+
"forces exit 0 for ALL outcomes and therefore overrides this flag; do not "
819+
"combine the two if you want the custom code to take effect."
820+
)
821+
)
805822
advanced_group.add_argument(
806823
"--allow-unverified",
807824
action="store_true",

socketsecurity/socketcli.py

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,37 @@
2727

2828
load_dotenv()
2929

30+
# Buildkite sets BUILDKITE=true in every job environment. Used to gate log
31+
# section markers that would render as literal text on other CI platforms.
32+
IS_BUILDKITE = os.getenv("BUILDKITE") == "true"
33+
34+
35+
def _emit_infrastructure_error(message: str, include_traceback: bool = False) -> None:
36+
"""Emit a structured error for infrastructure/API failures.
37+
38+
When running in Buildkite, wraps the error in log-section markers
39+
(`^^^ +++` expands the section in the BK UI) and prints a soft_fail hint.
40+
On every other platform it's a plain log.error so the markers don't leak
41+
as literal text. This is presentation only -- it does not decide the exit
42+
code (the caller does that, honoring --disable-blocking and
43+
--exit-code-on-api-error).
44+
"""
45+
if IS_BUILDKITE:
46+
print("^^^ +++", flush=True)
47+
print("--- :warning: Socket infrastructure error", flush=True)
48+
49+
log.error(message)
50+
51+
if IS_BUILDKITE:
52+
log.error(
53+
"Tip: this is an infrastructure error, not a security finding. To keep it "
54+
"from blocking the build, add a soft_fail rule for the CLI's API-error exit "
55+
"code (default 3, or whatever you pass to --exit-code-on-api-error)."
56+
)
57+
58+
if include_traceback:
59+
traceback.print_exc()
60+
3061

3162
def build_license_artifact_payload(
3263
diff: Diff,
@@ -73,12 +104,17 @@ def cli():
73104
else:
74105
sys.exit(0)
75106
except Exception as error:
76-
log.error("Unexpected error when running the cli")
77-
log.error(error)
78-
traceback.print_exc()
79107
config = CliConfig.from_args() # Get current config
108+
_emit_infrastructure_error(
109+
f"Unexpected error when running the CLI: {error}",
110+
include_traceback=True,
111+
)
112+
# --disable-blocking forces a clean exit for ALL outcomes (it takes
113+
# precedence over --exit-code-on-api-error); otherwise infra/API errors
114+
# exit with the configurable code (default 3), keeping them distinct
115+
# from blocking security findings (exit 1).
80116
if not config.disable_blocking:
81-
sys.exit(3)
117+
sys.exit(config.exit_code_on_api_error)
82118
else:
83119
sys.exit(0)
84120

0 commit comments

Comments
 (0)