[{"data":1,"prerenderedAt":2035},["ShallowReactive",2],{"page-\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools\u002F":3,"content-directory":1789},{"id":4,"title":5,"body":6,"date":1773,"description":1774,"difficulty":1775,"draft":1776,"extension":1777,"meta":1778,"navigation":247,"path":1779,"seo":1780,"stem":1781,"tags":1782,"updated":1773,"__hash__":1788},"content\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools\u002Findex.md","Choosing Exit Codes for CLI Tools",{"type":7,"value":8,"toc":1754},"minimark",[9,48,53,128,132,135,139,163,184,283,293,297,300,345,348,453,469,473,488,491,629,632,674,702,706,709,712,794,812,844,848,858,1015,1038,1188,1203,1207,1213,1324,1330,1334,1340,1458,1464,1583,1594,1598,1680,1684,1689,1692,1696,1699,1703,1706,1710,1713,1717,1720,1724,1750],[10,11,12,13,17,18,17,21,24,25,28,29,28,32,35,36,39,40,43,44,47],"p",{},"Every time your CLI exits, it hands the shell a single integer. That number is the only thing ",[14,15,16],"code",{},"&&",", ",[14,19,20],{},"||",[14,22,23],{},"set -e",", and CI gates ever look at — the text you printed is invisible to them. This guide covers which numbers to use: the ",[14,26,27],{},"0","\u002F",[14,30,31],{},"1",[14,33,34],{},"2"," baseline, the ",[14,37,38],{},"sysexits.h"," set and when it earns its keep, the reserved range above ",[14,41,42],{},"125",", and how to model your codes as one ",[14,45,46],{},"IntEnum"," you can document and test.",[49,50,52],"h2",{"id":51},"tldr","TL;DR",[54,55,56,65,80,93,110],"ul",{},[57,58,59,61,62,64],"li",{},[14,60,27],{}," is success. Everything non-zero is failure. Never exit ",[14,63,27],{}," on an error path.",[57,66,67,69,70,72,73,76,77,79],{},[14,68,31],{}," is a general error; ",[14,71,34],{}," is a usage error (bad flags\u002Fargs) — ",[14,74,75],{},"argparse"," and Click already use ",[14,78,34],{}," for this, so match them.",[57,81,82,83,85,86,17,89,92],{},"The ",[14,84,38],{}," codes (",[14,87,88],{},"EX_USAGE 64",[14,90,91],{},"EX_DATAERR 65",", …) give richer semantics. Adopt them only if your callers actually branch on them; otherwise they add noise.",[57,94,95,96,17,99,17,102,105,106,109],{},"Values ",[14,97,98],{},"126",[14,100,101],{},"127",[14,103,104],{},"128",", and ",[14,107,108],{},"128+N"," are reserved by the shell (not executable, not found, killed by signal N). Stay out of that range.",[57,111,112,113,115,116,119,120,123,124,127],{},"Define your codes once as an ",[14,114,46],{},", document them in ",[14,117,118],{},"--help"," or the man page, and assert them in tests with ",[14,121,122],{},"CliRunner"," or ",[14,125,126],{},"subprocess",".",[49,129,131],{"id":130},"start-with-0-1-and-2","Start with 0, 1, and 2",[10,133,134],{},"The bedrock convention is older than Python and universally understood:",[136,137],"inline-diagram",{"name":138},"exit-code-basics",[54,140,141,149,156],{},[57,142,143,148],{},[144,145,146],"strong",{},[14,147,27],{}," — success. The command did what was asked.",[57,150,151,155],{},[144,152,153],{},[14,154,31],{}," — a general, catch-all failure. Something went wrong and you have nothing more specific to say.",[57,157,158,162],{},[144,159,160],{},[14,161,34],{}," — a usage error: an unknown option, a missing required argument, a malformed value. The user needs to fix the command line, not the world.",[10,164,165,166,168,169,171,172,174,175,177,178,180,181,183],{},"That ",[14,167,34],{},"-means-usage split is not arbitrary. Both ",[14,170,75],{}," and Click exit ",[14,173,34],{}," when parsing fails, so if you invent your own error handling you should keep ",[14,176,34],{}," reserved for the same meaning. Otherwise a script that treats ",[14,179,34],{}," as \"retry with different args\" gets confused when your tool returns ",[14,182,34],{}," for a network error.",[185,186,191],"pre",{"className":187,"code":188,"language":189,"meta":190,"style":190},"language-bash shiki shiki-themes github-light github-dark","$ mytool deploy --nonsuch\nUsage: mytool deploy [OPTIONS]\nTry 'mytool deploy --help' for help.\n\nError: No such option: --nonsuch\n$ echo $?\n2\n","bash","",[14,192,193,213,227,242,249,266,277],{"__ignoreMap":190},[194,195,198,202,206,209],"span",{"class":196,"line":197},"line",1,[194,199,201],{"class":200},"sScJk","$",[194,203,205],{"class":204},"sZZnC"," mytool",[194,207,208],{"class":204}," deploy",[194,210,212],{"class":211},"sj4cs"," --nonsuch\n",[194,214,216,219,221,223],{"class":196,"line":215},2,[194,217,218],{"class":200},"Usage:",[194,220,205],{"class":204},[194,222,208],{"class":204},[194,224,226],{"class":225},"sVt8B"," [OPTIONS]\n",[194,228,230,233,236,239],{"class":196,"line":229},3,[194,231,232],{"class":200},"Try",[194,234,235],{"class":204}," 'mytool deploy --help'",[194,237,238],{"class":204}," for",[194,240,241],{"class":204}," help.\n",[194,243,245],{"class":196,"line":244},4,[194,246,248],{"emptyLinePlaceholder":247},true,"\n",[194,250,252,255,258,261,264],{"class":196,"line":251},5,[194,253,254],{"class":200},"Error:",[194,256,257],{"class":204}," No",[194,259,260],{"class":204}," such",[194,262,263],{"class":204}," option:",[194,265,212],{"class":211},[194,267,269,271,274],{"class":196,"line":268},6,[194,270,201],{"class":200},[194,272,273],{"class":204}," echo",[194,275,276],{"class":211}," $?\n",[194,278,280],{"class":196,"line":279},7,[194,281,282],{"class":200},"2\n",[10,284,285,286,28,288,28,290,292],{},"For a great many tools, ",[14,287,27],{},[14,289,31],{},[14,291,34],{}," is the entire vocabulary you need. Reach for more only when a caller genuinely needs to distinguish failure modes programmatically.",[49,294,296],{"id":295},"when-distinct-failure-modes-earn-distinct-codes","When distinct failure modes earn distinct codes",[10,298,299],{},"Sometimes \"it failed\" is not enough. A backup tool might want CI to retry on a transient network failure but hard-stop on corrupted data. That is a real reason to hand out different numbers:",[185,301,305],{"className":302,"code":303,"language":304,"meta":190,"style":190},"language-python shiki shiki-themes github-light github-dark","raise SystemExit(3)   # network unreachable — safe to retry\nraise SystemExit(4)   # data integrity check failed — do NOT retry\n","python",[14,306,307,329],{"__ignoreMap":190},[194,308,309,313,316,319,322,325],{"class":196,"line":197},[194,310,312],{"class":311},"szBVR","raise",[194,314,315],{"class":211}," SystemExit",[194,317,318],{"class":225},"(",[194,320,321],{"class":211},"3",[194,323,324],{"class":225},")   ",[194,326,328],{"class":327},"sJ8bj","# network unreachable — safe to retry\n",[194,330,331,333,335,337,340,342],{"class":196,"line":215},[194,332,312],{"class":311},[194,334,315],{"class":211},[194,336,318],{"class":225},[194,338,339],{"class":211},"4",[194,341,324],{"class":225},[194,343,344],{"class":327},"# data integrity check failed — do NOT retry\n",[10,346,347],{},"Now a wrapper script can branch:",[185,349,351],{"className":187,"code":350,"language":189,"meta":190,"style":190},"mytool backup\ncase $? in\n  0) echo \"ok\" ;;\n  3) echo \"transient — retrying\"; retry ;;\n  4) echo \"corruption — paging oncall\"; page ;;\n  *) echo \"unknown failure\"; exit 1 ;;\nesac\n",[14,352,353,361,372,389,409,428,448],{"__ignoreMap":190},[194,354,355,358],{"class":196,"line":197},[194,356,357],{"class":200},"mytool",[194,359,360],{"class":204}," backup\n",[194,362,363,366,369],{"class":196,"line":215},[194,364,365],{"class":311},"case",[194,367,368],{"class":211}," $?",[194,370,371],{"class":311}," in\n",[194,373,374,378,381,383,386],{"class":196,"line":229},[194,375,377],{"class":376},"sA_wV","  0",[194,379,380],{"class":311},")",[194,382,273],{"class":211},[194,384,385],{"class":204}," \"ok\"",[194,387,388],{"class":225}," ;;\n",[194,390,391,394,396,398,401,404,407],{"class":196,"line":244},[194,392,393],{"class":376},"  3",[194,395,380],{"class":311},[194,397,273],{"class":211},[194,399,400],{"class":204}," \"transient — retrying\"",[194,402,403],{"class":225},"; ",[194,405,406],{"class":200},"retry",[194,408,388],{"class":225},[194,410,411,414,416,418,421,423,426],{"class":196,"line":251},[194,412,413],{"class":376},"  4",[194,415,380],{"class":311},[194,417,273],{"class":211},[194,419,420],{"class":204}," \"corruption — paging oncall\"",[194,422,403],{"class":225},[194,424,425],{"class":200},"page",[194,427,388],{"class":225},[194,429,430,433,435,438,440,443,446],{"class":196,"line":268},[194,431,432],{"class":311},"  *)",[194,434,273],{"class":211},[194,436,437],{"class":204}," \"unknown failure\"",[194,439,403],{"class":225},[194,441,442],{"class":211},"exit",[194,444,445],{"class":211}," 1",[194,447,388],{"class":225},[194,449,450],{"class":196,"line":279},[194,451,452],{"class":311},"esac\n",[10,454,455,456,459,460,462,463,468],{},"The test for whether a custom code is worth it is simple: ",[144,457,458],{},"will a caller ever behave differently because of it?"," If yes, define it. If the only consumer is a human reading the message, a plain ",[14,461,31],{}," with good error text is enough — see ",[464,465,467],"a",{"href":466},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Ffriendly-error-messages-and-tracebacks\u002F","friendly error messages and tracebacks"," for making that text actionable.",[49,470,472],{"id":471},"the-sysexitsh-codes","The sysexits.h codes",[10,474,475,476,479,480,483,484,487],{},"BSD's ",[14,477,478],{},"\u003Csysexits.h>"," defines a standard set of codes in the ",[14,481,482],{},"64","–",[14,485,486],{},"78"," range, meant to give failures a shared vocabulary across tools:",[136,489],{"name":490},"sysexits-matrix",[492,493,494,510],"table",{},[495,496,497],"thead",{},[498,499,500,504,507],"tr",{},[501,502,503],"th",{},"Code",[501,505,506],{},"Name",[501,508,509],{},"Meaning",[511,512,513,526,539,552,565,578,591,604,617],"tbody",{},[498,514,515,518,523],{},[516,517,482],"td",{},[516,519,520],{},[14,521,522],{},"EX_USAGE",[516,524,525],{},"Command used incorrectly (bad args)",[498,527,528,531,536],{},[516,529,530],{},"65",[516,532,533],{},[14,534,535],{},"EX_DATAERR",[516,537,538],{},"Input data was incorrect",[498,540,541,544,549],{},[516,542,543],{},"66",[516,545,546],{},[14,547,548],{},"EX_NOINPUT",[516,550,551],{},"An input file did not exist or was unreadable",[498,553,554,557,562],{},[516,555,556],{},"69",[516,558,559],{},[14,560,561],{},"EX_UNAVAILABLE",[516,563,564],{},"A required service is unavailable",[498,566,567,570,575],{},[516,568,569],{},"70",[516,571,572],{},[14,573,574],{},"EX_SOFTWARE",[516,576,577],{},"Internal software error",[498,579,580,583,588],{},[516,581,582],{},"73",[516,584,585],{},[14,586,587],{},"EX_CANTCREAT",[516,589,590],{},"Cannot create an output file",[498,592,593,596,601],{},[516,594,595],{},"74",[516,597,598],{},[14,599,600],{},"EX_IOERR",[516,602,603],{},"An I\u002FO error occurred",[498,605,606,609,614],{},[516,607,608],{},"77",[516,610,611],{},[14,612,613],{},"EX_NOPERM",[516,615,616],{},"Permission denied",[498,618,619,621,626],{},[516,620,486],{},[516,622,623],{},[14,624,625],{},"EX_CONFIG",[516,627,628],{},"Something is misconfigured",[10,630,631],{},"Python does not ship these as constants, so define the ones you use:",[185,633,635],{"className":302,"code":634,"language":304,"meta":190,"style":190},"EX_USAGE = 64\nEX_DATAERR = 65\nEX_NOINPUT = 66\nEX_CONFIG = 78\n",[14,636,637,647,656,665],{"__ignoreMap":190},[194,638,639,641,644],{"class":196,"line":197},[194,640,522],{"class":211},[194,642,643],{"class":311}," =",[194,645,646],{"class":211}," 64\n",[194,648,649,651,653],{"class":196,"line":215},[194,650,535],{"class":211},[194,652,643],{"class":311},[194,654,655],{"class":211}," 65\n",[194,657,658,660,662],{"class":196,"line":229},[194,659,548],{"class":211},[194,661,643],{"class":311},[194,663,664],{"class":211}," 66\n",[194,666,667,669,671],{"class":196,"line":244},[194,668,625],{"class":211},[194,670,643],{"class":311},[194,672,673],{"class":211}," 78\n",[10,675,676,679,680,682,683,686,687,689,690,483,692,694,695,28,697,28,699,701],{},[144,677,678],{},"When to bother:"," adopt ",[14,681,38],{}," if your tool lives in an ecosystem that already reads them — mail delivery agents, some init systems, tools invoked by ",[14,684,685],{},"xargs"," pipelines that inspect specific codes. For a typical developer CLI, most callers only distinguish ",[14,688,27],{}," from non-zero, and the ",[14,691,482],{},[14,693,486],{}," numbers are more obscure than a documented ",[14,696,31],{},[14,698,34],{},[14,700,321],{}," scheme of your own. Consistency and documentation beat conformance to a table nobody reads. Pick one convention and hold it across every subcommand.",[49,703,705],{"id":704},"reserved-values-above-125","Reserved values above 125",[10,707,708],{},"Some codes are not yours to assign — the shell claims them, and reusing them creates ambiguity:",[136,710],{"name":711},"exit-code-reserved-stack",[54,713,714,721,728,738,772],{},[57,715,716,720],{},[144,717,718],{},[14,719,98],{}," — the command was found but is not executable (permission problem).",[57,722,723,727],{},[144,724,725],{},[14,726,101],{}," — command not found.",[57,729,730,734,735,737],{},[144,731,732],{},[14,733,104],{}," — invalid argument to ",[14,736,442],{}," (e.g. a non-integer).",[57,739,740,745,746,749,750,753,754,757,758,753,761,764,765,753,768,771],{},[144,741,742],{},[14,743,744],{},"128 + N"," — the process was killed by signal ",[14,747,748],{},"N",". So ",[14,751,752],{},"130"," = ",[14,755,756],{},"128 + 2"," (SIGINT, a Ctrl-C), ",[14,759,760],{},"137",[14,762,763],{},"128 + 9"," (SIGKILL), ",[14,766,767],{},"143",[14,769,770],{},"128 + 15"," (SIGTERM).",[57,773,774,780,781,784,785,17,787,790,791,793],{},[144,775,776,777],{},"Above ",[14,778,779],{},"255"," — impossible. Exit status is 8 bits, so codes wrap modulo 256: ",[14,782,783],{},"exit(256)"," reports as ",[14,786,27],{},[14,788,789],{},"exit(257)"," as ",[14,792,31],{},". A code over 255 is a silent bug.",[10,795,796,797,805,806,808,809,811],{},"The practical rule: ",[144,798,799,800,483,802,804],{},"keep your own meaningful codes in the ",[14,801,31],{},[14,803,42],{}," range."," If a caller sees ",[14,807,752],{},", they should be able to conclude \"someone hit Ctrl-C,\" not \"the backup's canary check failed.\" Matching Ctrl-C to ",[14,810,752],{}," is a nicety worth implementing:",[185,813,815],{"className":302,"code":814,"language":304,"meta":190,"style":190},"except KeyboardInterrupt:\n    raise SystemExit(130)   # 128 + SIGINT\n",[14,816,817,828],{"__ignoreMap":190},[194,818,819,822,825],{"class":196,"line":197},[194,820,821],{"class":311},"except",[194,823,824],{"class":211}," KeyboardInterrupt",[194,826,827],{"class":225},":\n",[194,829,830,833,835,837,839,841],{"class":196,"line":215},[194,831,832],{"class":311},"    raise",[194,834,315],{"class":211},[194,836,318],{"class":225},[194,838,752],{"class":211},[194,840,324],{"class":225},[194,842,843],{"class":327},"# 128 + SIGINT\n",[49,845,847],{"id":846},"one-intenum-as-the-single-source-of-truth","One IntEnum as the single source of truth",[10,849,850,851,854,855,857],{},"Scattering magic numbers like ",[14,852,853],{},"raise SystemExit(4)"," across a codebase is how a ",[14,856,339],{}," comes to mean two different things in two commands. Centralize them:",[185,859,861],{"className":302,"code":860,"language":304,"meta":190,"style":190},"from enum import IntEnum\n\nclass ExitCode(IntEnum):\n    OK = 0\n    ERROR = 1          # general failure\n    USAGE = 2          # bad invocation\n    NETWORK = 3        # transient, retryable\n    DATA = 4           # corrupt input, do not retry\n    CONFIG = 5         # misconfiguration\n\n    def __str__(self) -> str:            # so f-strings show the number\n        return str(self.value)\n",[14,862,863,877,881,896,906,918,931,944,958,972,977,998],{"__ignoreMap":190},[194,864,865,868,871,874],{"class":196,"line":197},[194,866,867],{"class":311},"from",[194,869,870],{"class":225}," enum ",[194,872,873],{"class":311},"import",[194,875,876],{"class":225}," IntEnum\n",[194,878,879],{"class":196,"line":215},[194,880,248],{"emptyLinePlaceholder":247},[194,882,883,886,889,891,893],{"class":196,"line":229},[194,884,885],{"class":311},"class",[194,887,888],{"class":200}," ExitCode",[194,890,318],{"class":225},[194,892,46],{"class":200},[194,894,895],{"class":225},"):\n",[194,897,898,901,903],{"class":196,"line":244},[194,899,900],{"class":211},"    OK",[194,902,643],{"class":311},[194,904,905],{"class":211}," 0\n",[194,907,908,911,913,915],{"class":196,"line":251},[194,909,910],{"class":211},"    ERROR",[194,912,643],{"class":311},[194,914,445],{"class":211},[194,916,917],{"class":327},"          # general failure\n",[194,919,920,923,925,928],{"class":196,"line":268},[194,921,922],{"class":211},"    USAGE",[194,924,643],{"class":311},[194,926,927],{"class":211}," 2",[194,929,930],{"class":327},"          # bad invocation\n",[194,932,933,936,938,941],{"class":196,"line":279},[194,934,935],{"class":211},"    NETWORK",[194,937,643],{"class":311},[194,939,940],{"class":211}," 3",[194,942,943],{"class":327},"        # transient, retryable\n",[194,945,947,950,952,955],{"class":196,"line":946},8,[194,948,949],{"class":211},"    DATA",[194,951,643],{"class":311},[194,953,954],{"class":211}," 4",[194,956,957],{"class":327},"           # corrupt input, do not retry\n",[194,959,961,964,966,969],{"class":196,"line":960},9,[194,962,963],{"class":211},"    CONFIG",[194,965,643],{"class":311},[194,967,968],{"class":211}," 5",[194,970,971],{"class":327},"         # misconfiguration\n",[194,973,975],{"class":196,"line":974},10,[194,976,248],{"emptyLinePlaceholder":247},[194,978,980,983,986,989,992,995],{"class":196,"line":979},11,[194,981,982],{"class":311},"    def",[194,984,985],{"class":211}," __str__",[194,987,988],{"class":225},"(self) -> ",[194,990,991],{"class":211},"str",[194,993,994],{"class":225},":            ",[194,996,997],{"class":327},"# so f-strings show the number\n",[194,999,1001,1004,1007,1009,1012],{"class":196,"line":1000},12,[194,1002,1003],{"class":311},"        return",[194,1005,1006],{"class":211}," str",[194,1008,318],{"class":225},[194,1010,1011],{"class":211},"self",[194,1013,1014],{"class":225},".value)\n",[10,1016,1017,1018,1020,1021,1025,1026,1029,1030,1033,1034,1037],{},"Because ",[14,1019,46],{}," ",[1022,1023,1024],"em",{},"is"," an ",[14,1027,1028],{},"int",", you can hand it straight to ",[14,1031,1032],{},"sys.exit"," or return it from ",[14,1035,1036],{},"main",":",[185,1039,1041],{"className":302,"code":1040,"language":304,"meta":190,"style":190},"import sys\n\ndef main() -> ExitCode:\n    if not config_ok():\n        print(\"error: config invalid; see 'mytool config --check'\", file=sys.stderr)\n        return ExitCode.CONFIG\n    if not reachable():\n        print(\"error: registry unreachable\", file=sys.stderr)\n        return ExitCode.NETWORK\n    do_work()\n    return ExitCode.OK\n\nif __name__ == \"__main__\":\n    sys.exit(main())      # IntEnum → int, cleanly\n",[14,1042,1043,1050,1054,1065,1076,1098,1108,1117,1134,1143,1148,1158,1162,1179],{"__ignoreMap":190},[194,1044,1045,1047],{"class":196,"line":197},[194,1046,873],{"class":311},[194,1048,1049],{"class":225}," sys\n",[194,1051,1052],{"class":196,"line":215},[194,1053,248],{"emptyLinePlaceholder":247},[194,1055,1056,1059,1062],{"class":196,"line":229},[194,1057,1058],{"class":311},"def",[194,1060,1061],{"class":200}," main",[194,1063,1064],{"class":225},"() -> ExitCode:\n",[194,1066,1067,1070,1073],{"class":196,"line":244},[194,1068,1069],{"class":311},"    if",[194,1071,1072],{"class":311}," not",[194,1074,1075],{"class":225}," config_ok():\n",[194,1077,1078,1081,1083,1086,1088,1092,1095],{"class":196,"line":251},[194,1079,1080],{"class":211},"        print",[194,1082,318],{"class":225},[194,1084,1085],{"class":204},"\"error: config invalid; see 'mytool config --check'\"",[194,1087,17],{"class":225},[194,1089,1091],{"class":1090},"s4XuR","file",[194,1093,1094],{"class":311},"=",[194,1096,1097],{"class":225},"sys.stderr)\n",[194,1099,1100,1102,1105],{"class":196,"line":268},[194,1101,1003],{"class":311},[194,1103,1104],{"class":225}," ExitCode.",[194,1106,1107],{"class":211},"CONFIG\n",[194,1109,1110,1112,1114],{"class":196,"line":279},[194,1111,1069],{"class":311},[194,1113,1072],{"class":311},[194,1115,1116],{"class":225}," reachable():\n",[194,1118,1119,1121,1123,1126,1128,1130,1132],{"class":196,"line":946},[194,1120,1080],{"class":211},[194,1122,318],{"class":225},[194,1124,1125],{"class":204},"\"error: registry unreachable\"",[194,1127,17],{"class":225},[194,1129,1091],{"class":1090},[194,1131,1094],{"class":311},[194,1133,1097],{"class":225},[194,1135,1136,1138,1140],{"class":196,"line":960},[194,1137,1003],{"class":311},[194,1139,1104],{"class":225},[194,1141,1142],{"class":211},"NETWORK\n",[194,1144,1145],{"class":196,"line":974},[194,1146,1147],{"class":225},"    do_work()\n",[194,1149,1150,1153,1155],{"class":196,"line":979},[194,1151,1152],{"class":311},"    return",[194,1154,1104],{"class":225},[194,1156,1157],{"class":211},"OK\n",[194,1159,1160],{"class":196,"line":1000},[194,1161,248],{"emptyLinePlaceholder":247},[194,1163,1165,1168,1171,1174,1177],{"class":196,"line":1164},13,[194,1166,1167],{"class":311},"if",[194,1169,1170],{"class":211}," __name__",[194,1172,1173],{"class":311}," ==",[194,1175,1176],{"class":204}," \"__main__\"",[194,1178,827],{"class":225},[194,1180,1182,1185],{"class":196,"line":1181},14,[194,1183,1184],{"class":225},"    sys.exit(main())      ",[194,1186,1187],{"class":327},"# IntEnum → int, cleanly\n",[10,1189,1190,1191,1194,1195,1199,1200,127],{},"The enum becomes the one place you look to answer \"what does ",[14,1192,1193],{},"5"," mean?\" — and the names make the call sites self-documenting. This pairs naturally with the top-level error boundary described in the ",[464,1196,1198],{"href":1197},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002F","error handling and exit codes overview",", where each caught exception maps to one ",[14,1201,1202],{},"ExitCode",[49,1204,1206],{"id":1205},"documenting-your-codes","Documenting your codes",[10,1208,1209,1210,1212],{},"An exit code nobody can look up might as well be random. Publish the table where callers will find it — in ",[14,1211,118],{}," epilog text, a man page, or the README:",[185,1214,1216],{"className":302,"code":1215,"language":304,"meta":190,"style":190},"import click\n\nEPILOG = \"\"\"\\\nExit codes:\n  0  success\n  1  general error\n  2  usage error\n  3  network error (retryable)\n  4  data error (do not retry)\n  5  configuration error\n\"\"\"\n\n@click.command(epilog=EPILOG)\ndef cli() -> None:\n    ...\n",[14,1217,1218,1225,1229,1242,1247,1252,1257,1262,1267,1272,1277,1282,1286,1303,1318],{"__ignoreMap":190},[194,1219,1220,1222],{"class":196,"line":197},[194,1221,873],{"class":311},[194,1223,1224],{"class":225}," click\n",[194,1226,1227],{"class":196,"line":215},[194,1228,248],{"emptyLinePlaceholder":247},[194,1230,1231,1234,1236,1239],{"class":196,"line":229},[194,1232,1233],{"class":211},"EPILOG",[194,1235,643],{"class":311},[194,1237,1238],{"class":204}," \"\"\"",[194,1240,1241],{"class":211},"\\\n",[194,1243,1244],{"class":196,"line":244},[194,1245,1246],{"class":204},"Exit codes:\n",[194,1248,1249],{"class":196,"line":251},[194,1250,1251],{"class":204},"  0  success\n",[194,1253,1254],{"class":196,"line":268},[194,1255,1256],{"class":204},"  1  general error\n",[194,1258,1259],{"class":196,"line":279},[194,1260,1261],{"class":204},"  2  usage error\n",[194,1263,1264],{"class":196,"line":946},[194,1265,1266],{"class":204},"  3  network error (retryable)\n",[194,1268,1269],{"class":196,"line":960},[194,1270,1271],{"class":204},"  4  data error (do not retry)\n",[194,1273,1274],{"class":196,"line":974},[194,1275,1276],{"class":204},"  5  configuration error\n",[194,1278,1279],{"class":196,"line":979},[194,1280,1281],{"class":204},"\"\"\"\n",[194,1283,1284],{"class":196,"line":1000},[194,1285,248],{"emptyLinePlaceholder":247},[194,1287,1288,1291,1293,1296,1298,1300],{"class":196,"line":1164},[194,1289,1290],{"class":200},"@click.command",[194,1292,318],{"class":225},[194,1294,1295],{"class":1090},"epilog",[194,1297,1094],{"class":311},[194,1299,1233],{"class":211},[194,1301,1302],{"class":225},")\n",[194,1304,1305,1307,1310,1313,1316],{"class":196,"line":1181},[194,1306,1058],{"class":311},[194,1308,1309],{"class":200}," cli",[194,1311,1312],{"class":225},"() -> ",[194,1314,1315],{"class":211},"None",[194,1317,827],{"class":225},[194,1319,1321],{"class":196,"line":1320},15,[194,1322,1323],{"class":211},"    ...\n",[10,1325,1326,1327,1329],{},"Keeping the table next to the ",[14,1328,46],{}," — ideally generated from it — means the docs cannot drift from the code.",[49,1331,1333],{"id":1332},"testing-exit-codes","Testing exit codes",[10,1335,1336,1337,1339],{},"An exit code is a promise; test it like one. With Click's ",[14,1338,122],{}," you get the code without spawning a process:",[185,1341,1343],{"className":302,"code":1342,"language":304,"meta":190,"style":190},"from click.testing import CliRunner\nfrom mytool.cli import cli\n\ndef test_bad_flag_is_usage_error() -> None:\n    result = CliRunner().invoke(cli, [\"--nonsuch\"])\n    assert result.exit_code == 2\n\ndef test_missing_config_returns_config_code() -> None:\n    result = CliRunner().invoke(cli, [\"run\"])\n    assert result.exit_code == ExitCode.CONFIG\n",[14,1344,1345,1357,1369,1373,1386,1402,1416,1420,1433,1446],{"__ignoreMap":190},[194,1346,1347,1349,1352,1354],{"class":196,"line":197},[194,1348,867],{"class":311},[194,1350,1351],{"class":225}," click.testing ",[194,1353,873],{"class":311},[194,1355,1356],{"class":225}," CliRunner\n",[194,1358,1359,1361,1364,1366],{"class":196,"line":215},[194,1360,867],{"class":311},[194,1362,1363],{"class":225}," mytool.cli ",[194,1365,873],{"class":311},[194,1367,1368],{"class":225}," cli\n",[194,1370,1371],{"class":196,"line":229},[194,1372,248],{"emptyLinePlaceholder":247},[194,1374,1375,1377,1380,1382,1384],{"class":196,"line":244},[194,1376,1058],{"class":311},[194,1378,1379],{"class":200}," test_bad_flag_is_usage_error",[194,1381,1312],{"class":225},[194,1383,1315],{"class":211},[194,1385,827],{"class":225},[194,1387,1388,1391,1393,1396,1399],{"class":196,"line":251},[194,1389,1390],{"class":225},"    result ",[194,1392,1094],{"class":311},[194,1394,1395],{"class":225}," CliRunner().invoke(cli, [",[194,1397,1398],{"class":204},"\"--nonsuch\"",[194,1400,1401],{"class":225},"])\n",[194,1403,1404,1407,1410,1413],{"class":196,"line":268},[194,1405,1406],{"class":311},"    assert",[194,1408,1409],{"class":225}," result.exit_code ",[194,1411,1412],{"class":311},"==",[194,1414,1415],{"class":211}," 2\n",[194,1417,1418],{"class":196,"line":279},[194,1419,248],{"emptyLinePlaceholder":247},[194,1421,1422,1424,1427,1429,1431],{"class":196,"line":946},[194,1423,1058],{"class":311},[194,1425,1426],{"class":200}," test_missing_config_returns_config_code",[194,1428,1312],{"class":225},[194,1430,1315],{"class":211},[194,1432,827],{"class":225},[194,1434,1435,1437,1439,1441,1444],{"class":196,"line":960},[194,1436,1390],{"class":225},[194,1438,1094],{"class":311},[194,1440,1395],{"class":225},[194,1442,1443],{"class":204},"\"run\"",[194,1445,1401],{"class":225},[194,1447,1448,1450,1452,1454,1456],{"class":196,"line":974},[194,1449,1406],{"class":311},[194,1451,1409],{"class":225},[194,1453,1412],{"class":311},[194,1455,1104],{"class":225},[194,1457,1107],{"class":211},[10,1459,1460,1461,1463],{},"For an end-to-end check that includes your real entry point and ",[14,1462,1032],{},", drive it as a subprocess:",[185,1465,1467],{"className":302,"code":1466,"language":304,"meta":190,"style":190},"import subprocess\n\ndef test_network_failure_exit_code() -> None:\n    proc = subprocess.run(\n        [\"mytool\", \"backup\", \"--registry\", \"http:\u002F\u002F127.0.0.1:1\"],\n        capture_output=True,\n        text=True,\n    )\n    assert proc.returncode == 3\n    assert \"unreachable\" in proc.stderr\n",[14,1468,1469,1476,1480,1493,1503,1529,1542,1553,1558,1570],{"__ignoreMap":190},[194,1470,1471,1473],{"class":196,"line":197},[194,1472,873],{"class":311},[194,1474,1475],{"class":225}," subprocess\n",[194,1477,1478],{"class":196,"line":215},[194,1479,248],{"emptyLinePlaceholder":247},[194,1481,1482,1484,1487,1489,1491],{"class":196,"line":229},[194,1483,1058],{"class":311},[194,1485,1486],{"class":200}," test_network_failure_exit_code",[194,1488,1312],{"class":225},[194,1490,1315],{"class":211},[194,1492,827],{"class":225},[194,1494,1495,1498,1500],{"class":196,"line":244},[194,1496,1497],{"class":225},"    proc ",[194,1499,1094],{"class":311},[194,1501,1502],{"class":225}," subprocess.run(\n",[194,1504,1505,1508,1511,1513,1516,1518,1521,1523,1526],{"class":196,"line":251},[194,1506,1507],{"class":225},"        [",[194,1509,1510],{"class":204},"\"mytool\"",[194,1512,17],{"class":225},[194,1514,1515],{"class":204},"\"backup\"",[194,1517,17],{"class":225},[194,1519,1520],{"class":204},"\"--registry\"",[194,1522,17],{"class":225},[194,1524,1525],{"class":204},"\"http:\u002F\u002F127.0.0.1:1\"",[194,1527,1528],{"class":225},"],\n",[194,1530,1531,1534,1536,1539],{"class":196,"line":268},[194,1532,1533],{"class":1090},"        capture_output",[194,1535,1094],{"class":311},[194,1537,1538],{"class":211},"True",[194,1540,1541],{"class":225},",\n",[194,1543,1544,1547,1549,1551],{"class":196,"line":279},[194,1545,1546],{"class":1090},"        text",[194,1548,1094],{"class":311},[194,1550,1538],{"class":211},[194,1552,1541],{"class":225},[194,1554,1555],{"class":196,"line":946},[194,1556,1557],{"class":225},"    )\n",[194,1559,1560,1562,1565,1567],{"class":196,"line":960},[194,1561,1406],{"class":311},[194,1563,1564],{"class":225}," proc.returncode ",[194,1566,1412],{"class":311},[194,1568,1569],{"class":211}," 3\n",[194,1571,1572,1574,1577,1580],{"class":196,"line":974},[194,1573,1406],{"class":311},[194,1575,1576],{"class":204}," \"unreachable\"",[194,1578,1579],{"class":311}," in",[194,1581,1582],{"class":225}," proc.stderr\n",[10,1584,1585,1586,1589,1590,127],{},"Note the second test also asserts the message went to ",[144,1587,1588],{},"stderr",", not stdout — the two promises a good failure keeps. For a broader look at exercising a CLI in tests, the entry-point mechanics are covered in ",[464,1591,1593],{"href":1592},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fbest-practices-for-python-cli-entry-points\u002F","best practices for Python CLI entry points",[49,1595,1597],{"id":1596},"production-notes","Production notes",[54,1599,1600,1616,1637,1660,1671],{},[57,1601,1602,1020,1605,1608,1609,1611,1612,483,1614,127],{},[144,1603,1604],{},"Wrapping bites silently.",[14,1606,1607],{},"sys.exit(256)"," exits ",[14,1610,27],{},". If codes are computed, clamp or assert they stay in ",[14,1613,27],{},[14,1615,42],{},[57,1617,1618,1621,1622,1624,1625,1627,1628,403,1631,1608,1634,1636],{},[144,1619,1620],{},"Click and Typer own some codes."," Click exits ",[14,1623,34],{}," on parse errors and ",[14,1626,31],{}," for ",[14,1629,1630],{},"ClickException",[14,1632,1633],{},"Abort",[14,1635,31],{},". Don't reassign those meanings in the same app.",[57,1638,1639,1644,1645,1648,1649,1652,1653,1655,1656,1659],{},[144,1640,1641,1643],{},[14,1642,23],{}," and pipelines."," In ",[14,1646,1647],{},"a | b",", the shell reports ",[14,1650,1651],{},"b","'s code by default. Callers who need ",[14,1654,464],{},"'s status use ",[14,1657,1658],{},"set -o pipefail"," — document that your meaningful codes may be masked mid-pipe.",[57,1661,1662,1665,1666,17,1668,1670],{},[144,1663,1664],{},"Cross-platform."," Signal-derived codes (",[14,1667,752],{},[14,1669,767],{},") are a Unix convention; Windows reports different values. Keep the codes you rely on in the low range for portability.",[57,1672,1673,1676,1677,1679],{},[144,1674,1675],{},"CI gates."," Most CI systems treat any non-zero as a failed step. If you use codes like ",[14,1678,321],{}," for \"retryable,\" make sure the wrapper — not the raw CI step — is what interprets them.",[49,1681,1683],{"id":1682},"frequently-asked-questions","Frequently asked questions",[1685,1686,1688],"h3",{"id":1687},"does-an-exit-code-above-1-mean-anything-special-to-the-shell","Does an exit code above 1 mean anything special to the shell?",[10,1690,1691],{},"Between 0 and 125 the meaning is entirely yours — the shell only cares that 0 is success and anything else is failure. From 126 upward the values are already spoken for: 126 means the command was found but could not be executed, 127 means it was not found at all, and 128 + N means the process was killed by signal N (130 for Ctrl-C, 137 for SIGKILL). Returning one of those yourself makes a normal failure look like a crash to anyone reading a CI log.",[1685,1693,1695],{"id":1694},"should-i-use-the-sysexitsh-codes","Should I use the sysexits.h codes?",[10,1697,1698],{},"Only if your users script against them. The sysexits values (64 for a usage error, 65 for bad data, 69 for an unavailable service, 78 for a configuration problem) are a genuine convention on BSD-derived systems, and they are a good vocabulary to borrow when you need more than pass\u002Ffail. But an undocumented 69 tells a reader no more than an undocumented 1. The value comes from documenting the codes and keeping them stable across releases, not from the numbers themselves.",[1685,1700,1702],{"id":1701},"how-do-i-keep-exit-codes-consistent-across-a-large-cli","How do I keep exit codes consistent across a large CLI?",[10,1704,1705],{},"Define them once in an IntEnum and never write a bare integer anywhere else. The enum gives each code a name, a docstring and a single place to review when you add one, and because IntEnum members are integers you can pass them straight to sys.exit or typer.Exit. Commands then raise a domain exception, and one top-level error boundary maps that exception to the enum member — which means no command function has to know the numbers at all.",[1685,1707,1709],{"id":1708},"is-it-safe-to-change-an-exit-code-in-a-new-release","Is it safe to change an exit code in a new release?",[10,1711,1712],{},"Treat it as a breaking change. Exit codes are part of a CLI's public interface in exactly the way flags are: a script somewhere is testing for exit code 2, and changing what that means will silently alter a conditional in someone's pipeline. Add a new code rather than repurposing an old one, and if a change is unavoidable, bump the major version and say so in the changelog.",[1685,1714,1716],{"id":1715},"what-exit-code-should-my-tool-use-when-the-user-presses-ctrl-c","What exit code should my tool use when the user presses Ctrl-C?",[10,1718,1719],{},"Let it exit with 130, which is what 128 + SIGINT works out to, and make sure you do not swallow the interrupt. Catch KeyboardInterrupt only to clean up (close a file, remove a partial download, restore the cursor), then re-raise it or call sys.exit(130). Printing a traceback for a deliberate interrupt looks like a crash, and exiting 0 makes a cancelled run look successful to the calling script.",[49,1721,1723],{"id":1722},"related","Related",[54,1725,1726,1732,1738,1744],{},[57,1727,1728,1729],{},"Up: ",[464,1730,1731],{"href":1197},"Error handling and exit codes for CLIs",[57,1733,1734,1735],{},"Sideways: ",[464,1736,1737],{"href":466},"Friendly error messages and tracebacks",[57,1739,1734,1740],{},[464,1741,1743],{"href":1742},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002F","Advanced argument validation strategies",[57,1745,1746,1747],{},"Related: ",[464,1748,1749],{"href":1592},"Best practices for Python CLI entry points",[1751,1752,1753],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sA_wV, html code.shiki .sA_wV{--shiki-default:#032F62;--shiki-dark:#DBEDFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":190,"searchDepth":215,"depth":215,"links":1755},[1756,1757,1758,1759,1760,1761,1762,1763,1764,1765,1772],{"id":51,"depth":215,"text":52},{"id":130,"depth":215,"text":131},{"id":295,"depth":215,"text":296},{"id":471,"depth":215,"text":472},{"id":704,"depth":215,"text":705},{"id":846,"depth":215,"text":847},{"id":1205,"depth":215,"text":1206},{"id":1332,"depth":215,"text":1333},{"id":1596,"depth":215,"text":1597},{"id":1682,"depth":215,"text":1683,"children":1766},[1767,1768,1769,1770,1771],{"id":1687,"depth":229,"text":1688},{"id":1694,"depth":229,"text":1695},{"id":1701,"depth":229,"text":1702},{"id":1708,"depth":229,"text":1709},{"id":1715,"depth":229,"text":1716},{"id":1722,"depth":215,"text":1723},"2026-07-05","Pick exit codes scripts can trust: the 0\u002F1\u002F2 convention, the sysexits.h codes, reserved values above 125, and how to signal distinct failure modes.","intermediate",false,"md",{},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools",{"title":5,"description":1774},"advanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools\u002Findex",[1783,1784,1785,1786,1787],"exit-codes","errors","cli","click","testing","d8vL1TlnT0W2s-hLiaOLxmXjji_W_xfflsXOGioB5D0",[1790,1793,1796,1799,1802,1805,1808,1811,1814,1817,1818,1821,1824,1827,1830,1833,1836,1839,1842,1845,1848,1851,1854,1857,1860,1863,1866,1869,1872,1875,1878,1881,1883,1886,1889,1892,1895,1898,1901,1904,1907,1910,1913,1916,1918,1921,1924,1927,1930,1933,1936,1939,1942,1945,1948,1951,1954,1957,1960,1963,1966,1969,1972,1975,1978,1981,1984,1987,1990,1993,1996,1999,2002,2005,2008,2011,2014,2017,2020,2023,2026,2029,2032],{"path":1791,"title":1792},"\u002Fabout","About Python CLI Toolcraft",{"path":1794,"title":1795},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies","Advanced Argument Validation Strategies",{"path":1797,"title":1798},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fparsing-nested-json-arguments-in-python-clis","Parsing Nested JSON Args in Python CLIs",{"path":1800,"title":1801},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fvalidating-file-and-directory-paths-in-clis","Validating File and Directory Paths in CLIs",{"path":1803,"title":1804},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fadding-examples-and-epilogs-to-help-output","Adding Examples and Epilogs to Help Output",{"path":1806,"title":1807},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fgenerating-man-pages-and-docs-from-a-cli","Generating Man Pages and Docs from a CLI",{"path":1809,"title":1810},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation","CLI Help Output and Documentation",{"path":1812,"title":1813},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fversioning-and-deprecating-cli-flags","Versioning and Deprecating CLI Flags",{"path":1815,"title":1816},"\u002Fadvanced-input-parsing-user-experience\u002Fcli-help-output-and-documentation\u002Fwriting-help-text-users-actually-read","Writing Help Text Users Actually Read",{"path":1779,"title":5},{"path":1819,"title":1820},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Ffriendly-error-messages-and-tracebacks","Friendly Error Messages and Tracebacks",{"path":1822,"title":1823},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fhandling-keyboard-interrupt-cleanly","Handling Keyboard Interrupt Cleanly",{"path":1825,"title":1826},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes","Error Handling and Exit Codes for CLIs",{"path":1828,"title":1829},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults","Config Precedence: Flags, Env, Files, Defaults",{"path":1831,"title":1832},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars","Handling Config Files and Env Vars in CLIs",{"path":1834,"title":1835},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Floading-yaml-configs-safely-in-cli-apps","Loading YAML configs safely in CLI apps",{"path":1837,"title":1838},"\u002Fadvanced-input-parsing-user-experience","Advanced Input Parsing for Python CLIs",{"path":1840,"title":1841},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Fadding-progress-bars-and-spinners-to-python-clis","Progress Bars and Spinners for Python CLIs",{"path":1843,"title":1844},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich","Interactive Terminal UI with Rich",{"path":1846,"title":1847},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich\u002Frendering-tables-and-json-with-rich","Rendering Tables and JSON with Rich",{"path":1849,"title":1850},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Fenabling-tab-completion-in-click-and-typer","Enabling Tab Completion in Click and Typer",{"path":1852,"title":1853},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis","Shell Completion for Python CLIs",{"path":1855,"title":1856},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis\u002Finstalling-shell-completion-for-bash-zsh-fish","Installing Shell Completion for bash, zsh, fish",{"path":1858,"title":1859},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags","Adding Verbose and Quiet Logging Flags",{"path":1861,"title":1862},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps","Structured Logging for CLI Apps",{"path":1864,"title":1865},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fstructured-json-logging-in-python-clis","Structured JSON Logging in Python CLIs",{"path":1867,"title":1868},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fdetecting-tty-and-adapting-output","Detecting a TTY and Adapting Output",{"path":1870,"title":1871},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Femitting-json-output-for-scripting","Emitting JSON Output for Scripting",{"path":1873,"title":1874},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Fhandling-broken-pipe-and-sigpipe","Handling Broken Pipe and SIGPIPE",{"path":1876,"title":1877},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes","Working with stdin, stdout and Pipes",{"path":1879,"title":1880},"\u002Fadvanced-input-parsing-user-experience\u002Fworking-with-stdin-stdout-and-pipes\u002Freading-piped-input-in-python-clis","Reading Piped Input in Python CLIs",{"path":28,"title":1882},"Python CLI Toolcraft",{"path":1884,"title":1885},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading","CLI Startup Performance and Lazy Loading",{"path":1887,"title":1888},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Flazy-loading-subcommands-for-faster-startup","Lazy Loading Subcommands for Faster Startup",{"path":1890,"title":1891},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fprofiling-python-cli-startup-time","Profiling Python CLI Startup Time",{"path":1893,"title":1894},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Freducing-cli-dependency-weight","Reducing CLI Dependency Weight",{"path":1896,"title":1897},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands","argparse Subparsers for Subcommands",{"path":1899,"title":1900},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-vs-click-vs-typer-comparison","argparse vs Click vs Typer Compared",{"path":1902,"title":1903},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse","Command-Line Parsing with argparse",{"path":1905,"title":1906},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer","Migrating from argparse to Typer",{"path":1908,"title":1909},"\u002Fmodern-python-cli-frameworks-architecture","Python CLI Frameworks and Architecture",{"path":1911,"title":1912},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis","Plugin Architectures for Extensible CLIs",{"path":1914,"title":1915},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis\u002Fwriting-a-plugin-for-an-existing-cli","Writing a Plugin for an Existing CLI",{"path":1917,"title":1749},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fbest-practices-for-python-cli-entry-points",{"path":1919,"title":1920},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fdependency-injection-patterns-for-cli-commands","Dependency Injection Patterns for CLI Commands",{"path":1922,"title":1923},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fhow-to-structure-a-large-python-cli-project","Structuring a Large Python CLI Project",{"path":1925,"title":1926},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis","Structuring Multi-Command Python CLIs",{"path":1928,"title":1929},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-state-with-click-context-objects","Sharing State with Click Context Objects",{"path":1931,"title":1932},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications","Testing Python CLI Applications",{"path":1934,"title":1935},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fmeasuring-cli-test-coverage","Measuring CLI Test Coverage",{"path":1937,"title":1938},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fmocking-filesystem-and-network-in-cli-tests","Mocking the Filesystem and Network in CLI Tests",{"path":1940,"title":1941},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Fsnapshot-testing-cli-output","Snapshot Testing CLI Output",{"path":1943,"title":1944},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-click-commands-with-clirunner","Testing Click Commands with CliRunner",{"path":1946,"title":1947},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftesting-python-cli-applications\u002Ftesting-interactive-prompts-and-stdin","Testing Interactive Prompts and stdin",{"path":1949,"title":1950},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fbuilding-a-cli-with-subcommands-in-click","Building a CLI with subcommands in Click",{"path":1952,"title":1953},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Fconverting-a-click-app-to-typer","Converting a Click App to Typer",{"path":1955,"title":1956},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each","Typer vs Click: When to Use Each",{"path":1958,"title":1959},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Ftyper-callback-functions-explained","Typer callback functions explained",{"path":1961,"title":1962},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter\u002Fcopier-vs-cookiecutter-for-cli-templates","Copier vs Cookiecutter for CLI Templates",{"path":1964,"title":1965},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter","CLI Project Scaffolding with Cookiecutter",{"path":1967,"title":1968},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbuilding-cross-platform-release-binaries-in-ci","Building Cross-Platform Release Binaries in CI",{"path":1970,"title":1971},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fbundling-a-python-cli-with-pyinstaller","Bundling a Python CLI with PyInstaller",{"path":1973,"title":1974},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fhomebrew-and-scoop-packaging-for-python-clis","Homebrew and Scoop Packaging for Python CLIs",{"path":1976,"title":1977},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries","Distributing CLIs as Standalone Binaries",{"path":1979,"title":1980},"\u002Fproject-setup-dependency-management\u002Fdistributing-clis-as-standalone-binaries\u002Fshipping-a-cli-as-a-zipapp-with-shiv","Shipping a CLI as a Zipapp with shiv",{"path":1982,"title":1983},"\u002Fproject-setup-dependency-management","Project Setup & Dependency Management",{"path":1985,"title":1986},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fautomating-changelogs-with-conventional-commits","Automating Changelogs with Conventional Commits",{"path":1988,"title":1989},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fexposing-version-info-and-build-metadata","Exposing Version Info and Build Metadata",{"path":1991,"title":1992},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs","Managing CLI Versioning & Changelogs",{"path":1994,"title":1995},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis","Building Wheels and sdists for Python CLIs",{"path":1997,"title":1998},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution","Packaging Python CLIs for Distribution",{"path":2000,"title":2001},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx","Installing and Distributing CLIs with pipx",{"path":2003,"title":2004},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi","Publishing a Python CLI to PyPI",{"path":2006,"title":2007},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development","Poetry Workflows for CLI Development",{"path":2009,"title":2010},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-entry-points-and-scripts-for-clis","Poetry Entry Points and Scripts for CLIs",{"path":2012,"title":2013},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects","Pre-commit Hooks for CLI Projects",{"path":2015,"title":2016},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects\u002Fsetting-up-pre-commit-for-python-cli-repos","Setting up pre-commit for Python CLI repos",{"path":2018,"title":2019},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management","uv for Python CLI Dependency Management",{"path":2021,"title":2022},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-init-vs-poetry-init-for-cli-tools","uv init vs poetry init for CLI tools",{"path":2024,"title":2025},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management\u002Fuv-tool-install-vs-pipx-for-clis","uv tool install vs pipx for CLIs",{"path":2027,"title":2028},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices","Python CLI Env Isolation Best Practices",{"path":2030,"title":2031},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fmanaging-virtual-environments-for-cross-platform-clis","Managing Python CLI Virtual Environments",{"path":2033,"title":2034},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fpinning-the-python-version-for-a-cli","Pinning the Python Version for a CLI",1785614690028]