[{"data":1,"prerenderedAt":1882},["ShallowReactive",2],{"page-\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools\u002F":3,"content-directory":1732},{"id":4,"title":5,"body":6,"date":1716,"description":1717,"difficulty":1718,"draft":1719,"extension":1720,"meta":1721,"navigation":243,"path":1722,"seo":1723,"stem":1724,"tags":1725,"updated":1716,"__hash__":1731},"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":1704},"minimark",[9,48,53,128,132,135,159,180,279,289,293,296,341,344,449,465,469,484,622,625,667,695,699,702,784,802,834,838,848,1005,1028,1178,1193,1197,1203,1314,1320,1324,1330,1448,1454,1573,1584,1588,1670,1674,1700],[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:",[54,136,137,145,152],{},[57,138,139,144],{},[140,141,142],"strong",{},[14,143,27],{}," — success. The command did what was asked.",[57,146,147,151],{},[140,148,149],{},[14,150,31],{}," — a general, catch-all failure. Something went wrong and you have nothing more specific to say.",[57,153,154,158],{},[140,155,156],{},[14,157,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,160,161,162,164,165,167,168,170,171,173,174,176,177,179],{},"That ",[14,163,34],{},"-means-usage split is not arbitrary. Both ",[14,166,75],{}," and Click exit ",[14,169,34],{}," when parsing fails, so if you invent your own error handling you should keep ",[14,172,34],{}," reserved for the same meaning. Otherwise a script that treats ",[14,175,34],{}," as \"retry with different args\" gets confused when your tool returns ",[14,178,34],{}," for a network error.",[181,182,187],"pre",{"className":183,"code":184,"language":185,"meta":186,"style":186},"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,188,189,209,223,238,245,262,273],{"__ignoreMap":186},[190,191,194,198,202,205],"span",{"class":192,"line":193},"line",1,[190,195,197],{"class":196},"sScJk","$",[190,199,201],{"class":200},"sZZnC"," mytool",[190,203,204],{"class":200}," deploy",[190,206,208],{"class":207},"sj4cs"," --nonsuch\n",[190,210,212,215,217,219],{"class":192,"line":211},2,[190,213,214],{"class":196},"Usage:",[190,216,201],{"class":200},[190,218,204],{"class":200},[190,220,222],{"class":221},"sVt8B"," [OPTIONS]\n",[190,224,226,229,232,235],{"class":192,"line":225},3,[190,227,228],{"class":196},"Try",[190,230,231],{"class":200}," 'mytool deploy --help'",[190,233,234],{"class":200}," for",[190,236,237],{"class":200}," help.\n",[190,239,241],{"class":192,"line":240},4,[190,242,244],{"emptyLinePlaceholder":243},true,"\n",[190,246,248,251,254,257,260],{"class":192,"line":247},5,[190,249,250],{"class":196},"Error:",[190,252,253],{"class":200}," No",[190,255,256],{"class":200}," such",[190,258,259],{"class":200}," option:",[190,261,208],{"class":207},[190,263,265,267,270],{"class":192,"line":264},6,[190,266,197],{"class":196},[190,268,269],{"class":200}," echo",[190,271,272],{"class":207}," $?\n",[190,274,276],{"class":192,"line":275},7,[190,277,278],{"class":196},"2\n",[10,280,281,282,28,284,28,286,288],{},"For a great many tools, ",[14,283,27],{},[14,285,31],{},[14,287,34],{}," is the entire vocabulary you need. Reach for more only when a caller genuinely needs to distinguish failure modes programmatically.",[49,290,292],{"id":291},"when-distinct-failure-modes-earn-distinct-codes","When distinct failure modes earn distinct codes",[10,294,295],{},"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:",[181,297,301],{"className":298,"code":299,"language":300,"meta":186,"style":186},"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,302,303,325],{"__ignoreMap":186},[190,304,305,309,312,315,318,321],{"class":192,"line":193},[190,306,308],{"class":307},"szBVR","raise",[190,310,311],{"class":207}," SystemExit",[190,313,314],{"class":221},"(",[190,316,317],{"class":207},"3",[190,319,320],{"class":221},")   ",[190,322,324],{"class":323},"sJ8bj","# network unreachable — safe to retry\n",[190,326,327,329,331,333,336,338],{"class":192,"line":211},[190,328,308],{"class":307},[190,330,311],{"class":207},[190,332,314],{"class":221},[190,334,335],{"class":207},"4",[190,337,320],{"class":221},[190,339,340],{"class":323},"# data integrity check failed — do NOT retry\n",[10,342,343],{},"Now a wrapper script can branch:",[181,345,347],{"className":183,"code":346,"language":185,"meta":186,"style":186},"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,348,349,357,368,385,405,424,444],{"__ignoreMap":186},[190,350,351,354],{"class":192,"line":193},[190,352,353],{"class":196},"mytool",[190,355,356],{"class":200}," backup\n",[190,358,359,362,365],{"class":192,"line":211},[190,360,361],{"class":307},"case",[190,363,364],{"class":207}," $?",[190,366,367],{"class":307}," in\n",[190,369,370,374,377,379,382],{"class":192,"line":225},[190,371,373],{"class":372},"sA_wV","  0",[190,375,376],{"class":307},")",[190,378,269],{"class":207},[190,380,381],{"class":200}," \"ok\"",[190,383,384],{"class":221}," ;;\n",[190,386,387,390,392,394,397,400,403],{"class":192,"line":240},[190,388,389],{"class":372},"  3",[190,391,376],{"class":307},[190,393,269],{"class":207},[190,395,396],{"class":200}," \"transient — retrying\"",[190,398,399],{"class":221},"; ",[190,401,402],{"class":196},"retry",[190,404,384],{"class":221},[190,406,407,410,412,414,417,419,422],{"class":192,"line":247},[190,408,409],{"class":372},"  4",[190,411,376],{"class":307},[190,413,269],{"class":207},[190,415,416],{"class":200}," \"corruption — paging oncall\"",[190,418,399],{"class":221},[190,420,421],{"class":196},"page",[190,423,384],{"class":221},[190,425,426,429,431,434,436,439,442],{"class":192,"line":264},[190,427,428],{"class":307},"  *)",[190,430,269],{"class":207},[190,432,433],{"class":200}," \"unknown failure\"",[190,435,399],{"class":221},[190,437,438],{"class":207},"exit",[190,440,441],{"class":207}," 1",[190,443,384],{"class":221},[190,445,446],{"class":192,"line":275},[190,447,448],{"class":307},"esac\n",[10,450,451,452,455,456,458,459,464],{},"The test for whether a custom code is worth it is simple: ",[140,453,454],{},"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,457,31],{}," with good error text is enough — see ",[460,461,463],"a",{"href":462},"\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,466,468],{"id":467},"the-sysexitsh-codes","The sysexits.h codes",[10,470,471,472,475,476,479,480,483],{},"BSD's ",[14,473,474],{},"\u003Csysexits.h>"," defines a standard set of codes in the ",[14,477,478],{},"64","–",[14,481,482],{},"78"," range, meant to give failures a shared vocabulary across tools:",[485,486,487,503],"table",{},[488,489,490],"thead",{},[491,492,493,497,500],"tr",{},[494,495,496],"th",{},"Code",[494,498,499],{},"Name",[494,501,502],{},"Meaning",[504,505,506,519,532,545,558,571,584,597,610],"tbody",{},[491,507,508,511,516],{},[509,510,478],"td",{},[509,512,513],{},[14,514,515],{},"EX_USAGE",[509,517,518],{},"Command used incorrectly (bad args)",[491,520,521,524,529],{},[509,522,523],{},"65",[509,525,526],{},[14,527,528],{},"EX_DATAERR",[509,530,531],{},"Input data was incorrect",[491,533,534,537,542],{},[509,535,536],{},"66",[509,538,539],{},[14,540,541],{},"EX_NOINPUT",[509,543,544],{},"An input file did not exist or was unreadable",[491,546,547,550,555],{},[509,548,549],{},"69",[509,551,552],{},[14,553,554],{},"EX_UNAVAILABLE",[509,556,557],{},"A required service is unavailable",[491,559,560,563,568],{},[509,561,562],{},"70",[509,564,565],{},[14,566,567],{},"EX_SOFTWARE",[509,569,570],{},"Internal software error",[491,572,573,576,581],{},[509,574,575],{},"73",[509,577,578],{},[14,579,580],{},"EX_CANTCREAT",[509,582,583],{},"Cannot create an output file",[491,585,586,589,594],{},[509,587,588],{},"74",[509,590,591],{},[14,592,593],{},"EX_IOERR",[509,595,596],{},"An I\u002FO error occurred",[491,598,599,602,607],{},[509,600,601],{},"77",[509,603,604],{},[14,605,606],{},"EX_NOPERM",[509,608,609],{},"Permission denied",[491,611,612,614,619],{},[509,613,482],{},[509,615,616],{},[14,617,618],{},"EX_CONFIG",[509,620,621],{},"Something is misconfigured",[10,623,624],{},"Python does not ship these as constants, so define the ones you use:",[181,626,628],{"className":298,"code":627,"language":300,"meta":186,"style":186},"EX_USAGE = 64\nEX_DATAERR = 65\nEX_NOINPUT = 66\nEX_CONFIG = 78\n",[14,629,630,640,649,658],{"__ignoreMap":186},[190,631,632,634,637],{"class":192,"line":193},[190,633,515],{"class":207},[190,635,636],{"class":307}," =",[190,638,639],{"class":207}," 64\n",[190,641,642,644,646],{"class":192,"line":211},[190,643,528],{"class":207},[190,645,636],{"class":307},[190,647,648],{"class":207}," 65\n",[190,650,651,653,655],{"class":192,"line":225},[190,652,541],{"class":207},[190,654,636],{"class":307},[190,656,657],{"class":207}," 66\n",[190,659,660,662,664],{"class":192,"line":240},[190,661,618],{"class":207},[190,663,636],{"class":307},[190,665,666],{"class":207}," 78\n",[10,668,669,672,673,675,676,679,680,682,683,479,685,687,688,28,690,28,692,694],{},[140,670,671],{},"When to bother:"," adopt ",[14,674,38],{}," if your tool lives in an ecosystem that already reads them — mail delivery agents, some init systems, tools invoked by ",[14,677,678],{},"xargs"," pipelines that inspect specific codes. For a typical developer CLI, most callers only distinguish ",[14,681,27],{}," from non-zero, and the ",[14,684,478],{},[14,686,482],{}," numbers are more obscure than a documented ",[14,689,31],{},[14,691,34],{},[14,693,317],{}," scheme of your own. Consistency and documentation beat conformance to a table nobody reads. Pick one convention and hold it across every subcommand.",[49,696,698],{"id":697},"reserved-values-above-125","Reserved values above 125",[10,700,701],{},"Some codes are not yours to assign — the shell claims them, and reusing them creates ambiguity:",[54,703,704,711,718,728,762],{},[57,705,706,710],{},[140,707,708],{},[14,709,98],{}," — the command was found but is not executable (permission problem).",[57,712,713,717],{},[140,714,715],{},[14,716,101],{}," — command not found.",[57,719,720,724,725,727],{},[140,721,722],{},[14,723,104],{}," — invalid argument to ",[14,726,438],{}," (e.g. a non-integer).",[57,729,730,735,736,739,740,743,744,747,748,743,751,754,755,743,758,761],{},[140,731,732],{},[14,733,734],{},"128 + N"," — the process was killed by signal ",[14,737,738],{},"N",". So ",[14,741,742],{},"130"," = ",[14,745,746],{},"128 + 2"," (SIGINT, a Ctrl-C), ",[14,749,750],{},"137",[14,752,753],{},"128 + 9"," (SIGKILL), ",[14,756,757],{},"143",[14,759,760],{},"128 + 15"," (SIGTERM).",[57,763,764,770,771,774,775,17,777,780,781,783],{},[140,765,766,767],{},"Above ",[14,768,769],{},"255"," — impossible. Exit status is 8 bits, so codes wrap modulo 256: ",[14,772,773],{},"exit(256)"," reports as ",[14,776,27],{},[14,778,779],{},"exit(257)"," as ",[14,782,31],{},". A code over 255 is a silent bug.",[10,785,786,787,795,796,798,799,801],{},"The practical rule: ",[140,788,789,790,479,792,794],{},"keep your own meaningful codes in the ",[14,791,31],{},[14,793,42],{}," range."," If a caller sees ",[14,797,742],{},", they should be able to conclude \"someone hit Ctrl-C,\" not \"the backup's canary check failed.\" Matching Ctrl-C to ",[14,800,742],{}," is a nicety worth implementing:",[181,803,805],{"className":298,"code":804,"language":300,"meta":186,"style":186},"except KeyboardInterrupt:\n    raise SystemExit(130)   # 128 + SIGINT\n",[14,806,807,818],{"__ignoreMap":186},[190,808,809,812,815],{"class":192,"line":193},[190,810,811],{"class":307},"except",[190,813,814],{"class":207}," KeyboardInterrupt",[190,816,817],{"class":221},":\n",[190,819,820,823,825,827,829,831],{"class":192,"line":211},[190,821,822],{"class":307},"    raise",[190,824,311],{"class":207},[190,826,314],{"class":221},[190,828,742],{"class":207},[190,830,320],{"class":221},[190,832,833],{"class":323},"# 128 + SIGINT\n",[49,835,837],{"id":836},"one-intenum-as-the-single-source-of-truth","One IntEnum as the single source of truth",[10,839,840,841,844,845,847],{},"Scattering magic numbers like ",[14,842,843],{},"raise SystemExit(4)"," across a codebase is how a ",[14,846,335],{}," comes to mean two different things in two commands. Centralize them:",[181,849,851],{"className":298,"code":850,"language":300,"meta":186,"style":186},"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,852,853,867,871,886,896,908,921,934,948,962,967,988],{"__ignoreMap":186},[190,854,855,858,861,864],{"class":192,"line":193},[190,856,857],{"class":307},"from",[190,859,860],{"class":221}," enum ",[190,862,863],{"class":307},"import",[190,865,866],{"class":221}," IntEnum\n",[190,868,869],{"class":192,"line":211},[190,870,244],{"emptyLinePlaceholder":243},[190,872,873,876,879,881,883],{"class":192,"line":225},[190,874,875],{"class":307},"class",[190,877,878],{"class":196}," ExitCode",[190,880,314],{"class":221},[190,882,46],{"class":196},[190,884,885],{"class":221},"):\n",[190,887,888,891,893],{"class":192,"line":240},[190,889,890],{"class":207},"    OK",[190,892,636],{"class":307},[190,894,895],{"class":207}," 0\n",[190,897,898,901,903,905],{"class":192,"line":247},[190,899,900],{"class":207},"    ERROR",[190,902,636],{"class":307},[190,904,441],{"class":207},[190,906,907],{"class":323},"          # general failure\n",[190,909,910,913,915,918],{"class":192,"line":264},[190,911,912],{"class":207},"    USAGE",[190,914,636],{"class":307},[190,916,917],{"class":207}," 2",[190,919,920],{"class":323},"          # bad invocation\n",[190,922,923,926,928,931],{"class":192,"line":275},[190,924,925],{"class":207},"    NETWORK",[190,927,636],{"class":307},[190,929,930],{"class":207}," 3",[190,932,933],{"class":323},"        # transient, retryable\n",[190,935,937,940,942,945],{"class":192,"line":936},8,[190,938,939],{"class":207},"    DATA",[190,941,636],{"class":307},[190,943,944],{"class":207}," 4",[190,946,947],{"class":323},"           # corrupt input, do not retry\n",[190,949,951,954,956,959],{"class":192,"line":950},9,[190,952,953],{"class":207},"    CONFIG",[190,955,636],{"class":307},[190,957,958],{"class":207}," 5",[190,960,961],{"class":323},"         # misconfiguration\n",[190,963,965],{"class":192,"line":964},10,[190,966,244],{"emptyLinePlaceholder":243},[190,968,970,973,976,979,982,985],{"class":192,"line":969},11,[190,971,972],{"class":307},"    def",[190,974,975],{"class":207}," __str__",[190,977,978],{"class":221},"(self) -> ",[190,980,981],{"class":207},"str",[190,983,984],{"class":221},":            ",[190,986,987],{"class":323},"# so f-strings show the number\n",[190,989,991,994,997,999,1002],{"class":192,"line":990},12,[190,992,993],{"class":307},"        return",[190,995,996],{"class":207}," str",[190,998,314],{"class":221},[190,1000,1001],{"class":207},"self",[190,1003,1004],{"class":221},".value)\n",[10,1006,1007,1008,1010,1011,1015,1016,1019,1020,1023,1024,1027],{},"Because ",[14,1009,46],{}," ",[1012,1013,1014],"em",{},"is"," an ",[14,1017,1018],{},"int",", you can hand it straight to ",[14,1021,1022],{},"sys.exit"," or return it from ",[14,1025,1026],{},"main",":",[181,1029,1031],{"className":298,"code":1030,"language":300,"meta":186,"style":186},"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,1032,1033,1040,1044,1055,1066,1088,1098,1107,1124,1133,1138,1148,1152,1169],{"__ignoreMap":186},[190,1034,1035,1037],{"class":192,"line":193},[190,1036,863],{"class":307},[190,1038,1039],{"class":221}," sys\n",[190,1041,1042],{"class":192,"line":211},[190,1043,244],{"emptyLinePlaceholder":243},[190,1045,1046,1049,1052],{"class":192,"line":225},[190,1047,1048],{"class":307},"def",[190,1050,1051],{"class":196}," main",[190,1053,1054],{"class":221},"() -> ExitCode:\n",[190,1056,1057,1060,1063],{"class":192,"line":240},[190,1058,1059],{"class":307},"    if",[190,1061,1062],{"class":307}," not",[190,1064,1065],{"class":221}," config_ok():\n",[190,1067,1068,1071,1073,1076,1078,1082,1085],{"class":192,"line":247},[190,1069,1070],{"class":207},"        print",[190,1072,314],{"class":221},[190,1074,1075],{"class":200},"\"error: config invalid; see 'mytool config --check'\"",[190,1077,17],{"class":221},[190,1079,1081],{"class":1080},"s4XuR","file",[190,1083,1084],{"class":307},"=",[190,1086,1087],{"class":221},"sys.stderr)\n",[190,1089,1090,1092,1095],{"class":192,"line":264},[190,1091,993],{"class":307},[190,1093,1094],{"class":221}," ExitCode.",[190,1096,1097],{"class":207},"CONFIG\n",[190,1099,1100,1102,1104],{"class":192,"line":275},[190,1101,1059],{"class":307},[190,1103,1062],{"class":307},[190,1105,1106],{"class":221}," reachable():\n",[190,1108,1109,1111,1113,1116,1118,1120,1122],{"class":192,"line":936},[190,1110,1070],{"class":207},[190,1112,314],{"class":221},[190,1114,1115],{"class":200},"\"error: registry unreachable\"",[190,1117,17],{"class":221},[190,1119,1081],{"class":1080},[190,1121,1084],{"class":307},[190,1123,1087],{"class":221},[190,1125,1126,1128,1130],{"class":192,"line":950},[190,1127,993],{"class":307},[190,1129,1094],{"class":221},[190,1131,1132],{"class":207},"NETWORK\n",[190,1134,1135],{"class":192,"line":964},[190,1136,1137],{"class":221},"    do_work()\n",[190,1139,1140,1143,1145],{"class":192,"line":969},[190,1141,1142],{"class":307},"    return",[190,1144,1094],{"class":221},[190,1146,1147],{"class":207},"OK\n",[190,1149,1150],{"class":192,"line":990},[190,1151,244],{"emptyLinePlaceholder":243},[190,1153,1155,1158,1161,1164,1167],{"class":192,"line":1154},13,[190,1156,1157],{"class":307},"if",[190,1159,1160],{"class":207}," __name__",[190,1162,1163],{"class":307}," ==",[190,1165,1166],{"class":200}," \"__main__\"",[190,1168,817],{"class":221},[190,1170,1172,1175],{"class":192,"line":1171},14,[190,1173,1174],{"class":221},"    sys.exit(main())      ",[190,1176,1177],{"class":323},"# IntEnum → int, cleanly\n",[10,1179,1180,1181,1184,1185,1189,1190,127],{},"The enum becomes the one place you look to answer \"what does ",[14,1182,1183],{},"5"," mean?\" — and the names make the call sites self-documenting. This pairs naturally with the top-level error boundary described in the ",[460,1186,1188],{"href":1187},"\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,1191,1192],{},"ExitCode",[49,1194,1196],{"id":1195},"documenting-your-codes","Documenting your codes",[10,1198,1199,1200,1202],{},"An exit code nobody can look up might as well be random. Publish the table where callers will find it — in ",[14,1201,118],{}," epilog text, a man page, or the README:",[181,1204,1206],{"className":298,"code":1205,"language":300,"meta":186,"style":186},"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,1207,1208,1215,1219,1232,1237,1242,1247,1252,1257,1262,1267,1272,1276,1293,1308],{"__ignoreMap":186},[190,1209,1210,1212],{"class":192,"line":193},[190,1211,863],{"class":307},[190,1213,1214],{"class":221}," click\n",[190,1216,1217],{"class":192,"line":211},[190,1218,244],{"emptyLinePlaceholder":243},[190,1220,1221,1224,1226,1229],{"class":192,"line":225},[190,1222,1223],{"class":207},"EPILOG",[190,1225,636],{"class":307},[190,1227,1228],{"class":200}," \"\"\"",[190,1230,1231],{"class":207},"\\\n",[190,1233,1234],{"class":192,"line":240},[190,1235,1236],{"class":200},"Exit codes:\n",[190,1238,1239],{"class":192,"line":247},[190,1240,1241],{"class":200},"  0  success\n",[190,1243,1244],{"class":192,"line":264},[190,1245,1246],{"class":200},"  1  general error\n",[190,1248,1249],{"class":192,"line":275},[190,1250,1251],{"class":200},"  2  usage error\n",[190,1253,1254],{"class":192,"line":936},[190,1255,1256],{"class":200},"  3  network error (retryable)\n",[190,1258,1259],{"class":192,"line":950},[190,1260,1261],{"class":200},"  4  data error (do not retry)\n",[190,1263,1264],{"class":192,"line":964},[190,1265,1266],{"class":200},"  5  configuration error\n",[190,1268,1269],{"class":192,"line":969},[190,1270,1271],{"class":200},"\"\"\"\n",[190,1273,1274],{"class":192,"line":990},[190,1275,244],{"emptyLinePlaceholder":243},[190,1277,1278,1281,1283,1286,1288,1290],{"class":192,"line":1154},[190,1279,1280],{"class":196},"@click.command",[190,1282,314],{"class":221},[190,1284,1285],{"class":1080},"epilog",[190,1287,1084],{"class":307},[190,1289,1223],{"class":207},[190,1291,1292],{"class":221},")\n",[190,1294,1295,1297,1300,1303,1306],{"class":192,"line":1171},[190,1296,1048],{"class":307},[190,1298,1299],{"class":196}," cli",[190,1301,1302],{"class":221},"() -> ",[190,1304,1305],{"class":207},"None",[190,1307,817],{"class":221},[190,1309,1311],{"class":192,"line":1310},15,[190,1312,1313],{"class":207},"    ...\n",[10,1315,1316,1317,1319],{},"Keeping the table next to the ",[14,1318,46],{}," — ideally generated from it — means the docs cannot drift from the code.",[49,1321,1323],{"id":1322},"testing-exit-codes","Testing exit codes",[10,1325,1326,1327,1329],{},"An exit code is a promise; test it like one. With Click's ",[14,1328,122],{}," you get the code without spawning a process:",[181,1331,1333],{"className":298,"code":1332,"language":300,"meta":186,"style":186},"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,1334,1335,1347,1359,1363,1376,1392,1406,1410,1423,1436],{"__ignoreMap":186},[190,1336,1337,1339,1342,1344],{"class":192,"line":193},[190,1338,857],{"class":307},[190,1340,1341],{"class":221}," click.testing ",[190,1343,863],{"class":307},[190,1345,1346],{"class":221}," CliRunner\n",[190,1348,1349,1351,1354,1356],{"class":192,"line":211},[190,1350,857],{"class":307},[190,1352,1353],{"class":221}," mytool.cli ",[190,1355,863],{"class":307},[190,1357,1358],{"class":221}," cli\n",[190,1360,1361],{"class":192,"line":225},[190,1362,244],{"emptyLinePlaceholder":243},[190,1364,1365,1367,1370,1372,1374],{"class":192,"line":240},[190,1366,1048],{"class":307},[190,1368,1369],{"class":196}," test_bad_flag_is_usage_error",[190,1371,1302],{"class":221},[190,1373,1305],{"class":207},[190,1375,817],{"class":221},[190,1377,1378,1381,1383,1386,1389],{"class":192,"line":247},[190,1379,1380],{"class":221},"    result ",[190,1382,1084],{"class":307},[190,1384,1385],{"class":221}," CliRunner().invoke(cli, [",[190,1387,1388],{"class":200},"\"--nonsuch\"",[190,1390,1391],{"class":221},"])\n",[190,1393,1394,1397,1400,1403],{"class":192,"line":264},[190,1395,1396],{"class":307},"    assert",[190,1398,1399],{"class":221}," result.exit_code ",[190,1401,1402],{"class":307},"==",[190,1404,1405],{"class":207}," 2\n",[190,1407,1408],{"class":192,"line":275},[190,1409,244],{"emptyLinePlaceholder":243},[190,1411,1412,1414,1417,1419,1421],{"class":192,"line":936},[190,1413,1048],{"class":307},[190,1415,1416],{"class":196}," test_missing_config_returns_config_code",[190,1418,1302],{"class":221},[190,1420,1305],{"class":207},[190,1422,817],{"class":221},[190,1424,1425,1427,1429,1431,1434],{"class":192,"line":950},[190,1426,1380],{"class":221},[190,1428,1084],{"class":307},[190,1430,1385],{"class":221},[190,1432,1433],{"class":200},"\"run\"",[190,1435,1391],{"class":221},[190,1437,1438,1440,1442,1444,1446],{"class":192,"line":964},[190,1439,1396],{"class":307},[190,1441,1399],{"class":221},[190,1443,1402],{"class":307},[190,1445,1094],{"class":221},[190,1447,1097],{"class":207},[10,1449,1450,1451,1453],{},"For an end-to-end check that includes your real entry point and ",[14,1452,1022],{},", drive it as a subprocess:",[181,1455,1457],{"className":298,"code":1456,"language":300,"meta":186,"style":186},"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,1458,1459,1466,1470,1483,1493,1519,1532,1543,1548,1560],{"__ignoreMap":186},[190,1460,1461,1463],{"class":192,"line":193},[190,1462,863],{"class":307},[190,1464,1465],{"class":221}," subprocess\n",[190,1467,1468],{"class":192,"line":211},[190,1469,244],{"emptyLinePlaceholder":243},[190,1471,1472,1474,1477,1479,1481],{"class":192,"line":225},[190,1473,1048],{"class":307},[190,1475,1476],{"class":196}," test_network_failure_exit_code",[190,1478,1302],{"class":221},[190,1480,1305],{"class":207},[190,1482,817],{"class":221},[190,1484,1485,1488,1490],{"class":192,"line":240},[190,1486,1487],{"class":221},"    proc ",[190,1489,1084],{"class":307},[190,1491,1492],{"class":221}," subprocess.run(\n",[190,1494,1495,1498,1501,1503,1506,1508,1511,1513,1516],{"class":192,"line":247},[190,1496,1497],{"class":221},"        [",[190,1499,1500],{"class":200},"\"mytool\"",[190,1502,17],{"class":221},[190,1504,1505],{"class":200},"\"backup\"",[190,1507,17],{"class":221},[190,1509,1510],{"class":200},"\"--registry\"",[190,1512,17],{"class":221},[190,1514,1515],{"class":200},"\"http:\u002F\u002F127.0.0.1:1\"",[190,1517,1518],{"class":221},"],\n",[190,1520,1521,1524,1526,1529],{"class":192,"line":264},[190,1522,1523],{"class":1080},"        capture_output",[190,1525,1084],{"class":307},[190,1527,1528],{"class":207},"True",[190,1530,1531],{"class":221},",\n",[190,1533,1534,1537,1539,1541],{"class":192,"line":275},[190,1535,1536],{"class":1080},"        text",[190,1538,1084],{"class":307},[190,1540,1528],{"class":207},[190,1542,1531],{"class":221},[190,1544,1545],{"class":192,"line":936},[190,1546,1547],{"class":221},"    )\n",[190,1549,1550,1552,1555,1557],{"class":192,"line":950},[190,1551,1396],{"class":307},[190,1553,1554],{"class":221}," proc.returncode ",[190,1556,1402],{"class":307},[190,1558,1559],{"class":207}," 3\n",[190,1561,1562,1564,1567,1570],{"class":192,"line":964},[190,1563,1396],{"class":307},[190,1565,1566],{"class":200}," \"unreachable\"",[190,1568,1569],{"class":307}," in",[190,1571,1572],{"class":221}," proc.stderr\n",[10,1574,1575,1576,1579,1580,127],{},"Note the second test also asserts the message went to ",[140,1577,1578],{},"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 ",[460,1581,1583],{"href":1582},"\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,1585,1587],{"id":1586},"production-notes","Production notes",[54,1589,1590,1606,1627,1650,1661],{},[57,1591,1592,1010,1595,1598,1599,1601,1602,479,1604,127],{},[140,1593,1594],{},"Wrapping bites silently.",[14,1596,1597],{},"sys.exit(256)"," exits ",[14,1600,27],{},". If codes are computed, clamp or assert they stay in ",[14,1603,27],{},[14,1605,42],{},[57,1607,1608,1611,1612,1614,1615,1617,1618,399,1621,1598,1624,1626],{},[140,1609,1610],{},"Click and Typer own some codes."," Click exits ",[14,1613,34],{}," on parse errors and ",[14,1616,31],{}," for ",[14,1619,1620],{},"ClickException",[14,1622,1623],{},"Abort",[14,1625,31],{},". Don't reassign those meanings in the same app.",[57,1628,1629,1634,1635,1638,1639,1642,1643,1645,1646,1649],{},[140,1630,1631,1633],{},[14,1632,23],{}," and pipelines."," In ",[14,1636,1637],{},"a | b",", the shell reports ",[14,1640,1641],{},"b","'s code by default. Callers who need ",[14,1644,460],{},"'s status use ",[14,1647,1648],{},"set -o pipefail"," — document that your meaningful codes may be masked mid-pipe.",[57,1651,1652,1655,1656,17,1658,1660],{},[140,1653,1654],{},"Cross-platform."," Signal-derived codes (",[14,1657,742],{},[14,1659,757],{},") are a Unix convention; Windows reports different values. Keep the codes you rely on in the low range for portability.",[57,1662,1663,1666,1667,1669],{},[140,1664,1665],{},"CI gates."," Most CI systems treat any non-zero as a failed step. If you use codes like ",[14,1668,317],{}," for \"retryable,\" make sure the wrapper — not the raw CI step — is what interprets them.",[49,1671,1673],{"id":1672},"related","Related",[54,1675,1676,1682,1688,1694],{},[57,1677,1678,1679],{},"Up: ",[460,1680,1681],{"href":1187},"Error handling and exit codes for CLIs",[57,1683,1684,1685],{},"Sideways: ",[460,1686,1687],{"href":462},"Friendly error messages and tracebacks",[57,1689,1684,1690],{},[460,1691,1693],{"href":1692},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002F","Advanced argument validation strategies",[57,1695,1696,1697],{},"Related: ",[460,1698,1699],{"href":1582},"Best practices for Python CLI entry points",[1701,1702,1703],"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":186,"searchDepth":211,"depth":211,"links":1705},[1706,1707,1708,1709,1710,1711,1712,1713,1714,1715],{"id":51,"depth":211,"text":52},{"id":130,"depth":211,"text":131},{"id":291,"depth":211,"text":292},{"id":467,"depth":211,"text":468},{"id":697,"depth":211,"text":698},{"id":836,"depth":211,"text":837},{"id":1195,"depth":211,"text":1196},{"id":1322,"depth":211,"text":1323},{"id":1586,"depth":211,"text":1587},{"id":1672,"depth":211,"text":1673},"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":1717},"advanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Fchoosing-exit-codes-for-cli-tools\u002Findex",[1726,1727,1728,1729,1730],"exit-codes","errors","cli","click","testing","WxsthYZ_qb20pOA08efiHDCj2GtCqKt-HbPB1CLCHZc",[1733,1736,1739,1742,1743,1746,1749,1752,1755,1758,1761,1764,1767,1770,1773,1776,1779,1782,1785,1787,1790,1793,1796,1799,1802,1805,1808,1811,1813,1816,1819,1822,1825,1828,1831,1834,1837,1840,1843,1846,1849,1852,1855,1858,1861,1864,1867,1870,1873,1876,1879],{"path":1734,"title":1735},"\u002Fabout","About Python CLI Toolcraft",{"path":1737,"title":1738},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies","Advanced Argument Validation Strategies",{"path":1740,"title":1741},"\u002Fadvanced-input-parsing-user-experience\u002Fadvanced-argument-validation-strategies\u002Fparsing-nested-json-arguments-in-python-clis","Parsing Nested JSON Args in Python CLIs",{"path":1722,"title":5},{"path":1744,"title":1745},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes\u002Ffriendly-error-messages-and-tracebacks","Friendly Error Messages and Tracebacks",{"path":1747,"title":1748},"\u002Fadvanced-input-parsing-user-experience\u002Ferror-handling-and-exit-codes","Error Handling and Exit Codes for CLIs",{"path":1750,"title":1751},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars\u002Fconfig-precedence-flags-env-files-defaults","Config Precedence: Flags, Env, Files, Defaults",{"path":1753,"title":1754},"\u002Fadvanced-input-parsing-user-experience\u002Fhandling-configuration-files-env-vars","Handling Config Files and Env Vars in CLIs",{"path":1756,"title":1757},"\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":1759,"title":1760},"\u002Fadvanced-input-parsing-user-experience","Advanced Input Parsing for Python CLIs",{"path":1762,"title":1763},"\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":1765,"title":1766},"\u002Fadvanced-input-parsing-user-experience\u002Finteractive-terminal-ui-with-rich","Interactive Terminal UI with Rich",{"path":1768,"title":1769},"\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":1771,"title":1772},"\u002Fadvanced-input-parsing-user-experience\u002Fshell-completion-for-python-clis","Shell Completion for Python CLIs",{"path":1774,"title":1775},"\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":1777,"title":1778},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fadding-verbose-and-quiet-logging-flags","Adding Verbose and Quiet Logging Flags",{"path":1780,"title":1781},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps","Structured Logging for CLI Apps",{"path":1783,"title":1784},"\u002Fadvanced-input-parsing-user-experience\u002Fstructured-logging-for-cli-apps\u002Fstructured-json-logging-in-python-clis","Structured JSON Logging in Python CLIs",{"path":28,"title":1786},"Python CLI Toolcraft",{"path":1788,"title":1789},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading","CLI Startup Performance and Lazy Loading",{"path":1791,"title":1792},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Flazy-loading-subcommands-for-faster-startup","Lazy Loading Subcommands for Faster Startup",{"path":1794,"title":1795},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcli-startup-performance-and-lazy-loading\u002Fprofiling-python-cli-startup-time","Profiling Python CLI Startup Time",{"path":1797,"title":1798},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fargparse-subparsers-for-subcommands","argparse Subparsers for Subcommands",{"path":1800,"title":1801},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse","Command-Line Parsing with argparse",{"path":1803,"title":1804},"\u002Fmodern-python-cli-frameworks-architecture\u002Fcommand-line-parsing-with-argparse\u002Fmigrating-from-argparse-to-typer","Migrating from argparse to Typer",{"path":1806,"title":1807},"\u002Fmodern-python-cli-frameworks-architecture","Python CLI Frameworks and Architecture",{"path":1809,"title":1810},"\u002Fmodern-python-cli-frameworks-architecture\u002Fplugin-architectures-for-extensible-clis","Plugin Architectures for Extensible CLIs",{"path":1812,"title":1699},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fbest-practices-for-python-cli-entry-points",{"path":1814,"title":1815},"\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":1817,"title":1818},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis","Structuring Multi-Command Python CLIs",{"path":1820,"title":1821},"\u002Fmodern-python-cli-frameworks-architecture\u002Fstructuring-multi-command-python-clis\u002Fsharing-state-with-click-context-objects","Sharing State with Click Context Objects",{"path":1823,"title":1824},"\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":1826,"title":1827},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each","Typer vs Click: When to Use Each",{"path":1829,"title":1830},"\u002Fmodern-python-cli-frameworks-architecture\u002Ftyper-vs-click-when-to-use-each\u002Ftyper-callback-functions-explained","Typer callback functions explained",{"path":1832,"title":1833},"\u002Fproject-setup-dependency-management\u002Fcli-project-scaffolding-with-cookiecutter","CLI Project Scaffolding with Cookiecutter",{"path":1835,"title":1836},"\u002Fproject-setup-dependency-management","Project Setup & Dependency Management",{"path":1838,"title":1839},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs\u002Fautomating-changelogs-with-conventional-commits","Automating Changelogs with Conventional Commits",{"path":1841,"title":1842},"\u002Fproject-setup-dependency-management\u002Fmanaging-cli-versioning-changelogs","Managing CLI Versioning & Changelogs",{"path":1844,"title":1845},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fbuilding-wheels-and-sdists-for-python-clis","Building Wheels and sdists for Python CLIs",{"path":1847,"title":1848},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution","Packaging Python CLIs for Distribution",{"path":1850,"title":1851},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Finstalling-and-distributing-clis-with-pipx","Installing and Distributing CLIs with pipx",{"path":1853,"title":1854},"\u002Fproject-setup-dependency-management\u002Fpackaging-python-clis-for-distribution\u002Fpublishing-a-python-cli-to-pypi","Publishing a Python CLI to PyPI",{"path":1856,"title":1857},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development","Poetry Workflows for CLI Development",{"path":1859,"title":1860},"\u002Fproject-setup-dependency-management\u002Fpoetry-workflows-for-cli-development\u002Fpoetry-entry-points-and-scripts-for-clis","Poetry Entry Points and Scripts for CLIs",{"path":1862,"title":1863},"\u002Fproject-setup-dependency-management\u002Fpre-commit-hooks-for-cli-projects","Pre-commit Hooks for CLI Projects",{"path":1865,"title":1866},"\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":1868,"title":1869},"\u002Fproject-setup-dependency-management\u002Fuv-for-python-cli-dependency-management","uv for Python CLI Dependency Management",{"path":1871,"title":1872},"\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":1874,"title":1875},"\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":1877,"title":1878},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices","Python CLI Env Isolation Best Practices",{"path":1880,"title":1881},"\u002Fproject-setup-dependency-management\u002Fvirtual-environments-isolation-best-practices\u002Fmanaging-virtual-environments-for-cross-platform-clis","Managing Python CLI Virtual Environments",1783281867195]