Errors — python-heatclient — branch stable/2026.2
Branch
stable/2026.2@88120f11830d(5.3.0-2-g88120f1) — repository https://opendev.org/openstack/python-heatclient — analysed on 2026-11-01
[observed] means reproduced against the local stub, in the scenario named (_rewrite/scenarios_stable-2026.2.json) or a wire probe. The stub is not Heat: these runs say what the client does with given bytes, not what a real Heat answers.
1. Exit codes
| Code | Situation | Source |
|---|---|---|
| 0 | the command returned; also several silent no-ops listed in § 4 | cliff:cliff/command.py:196 |
| 1 | any exception during the command: CommandError, HTTP*, or a raw KeyError, IndexError, AttributeError. The text alone goes to stderr, with no traceback unless --debug | cliff:cliff/app.py:462-467 |
| 2 | argument errors from argparse, or an unknown command | [observed, G29 and the variants --all o, --reset why] |
No command of this package returns a value from command.Command, so none hits the "return value becomes the exit code" trap (cliff:cliff/command.py:196). The 11 such commands were read one by one: they return nothing or the None of a helper.
The heat shell has its own handling: exit 1 on any exception, 130 on interrupt, and it prints encodeutils.safe_encode(str(e)), so a Python bytes literal b'…' on stderr (heatclient/shell.py:599-613).
2. How a failure reaches the user
HTTP status to exception
SessionClient.request passes raise_exc=False to keystoneauth, then maps any 4xx or 5xx and any 300 through exc.from_response (heatclient/common/http.py:316-329, heatclient/exc.py:184-187). The table is built from class attributes (heatclient/exc.py:177-181).
| Status | Class | Note |
|---|---|---|
| 300 | HTTPMultipleChoices | fixed text: HTTPMultipleChoices (HTTP 300) Requested version of Heat API is not available. [observed, E08] |
| 400 | HTTPBadRequest | heatclient/exc.py:93 |
| 401 | HTTPUnauthorized | keystoneauth first re-authenticates and retries once (keystoneauth1:keystoneauth1/session.py:1130-1131) [observed, E09: 2 authentications, 2 requests] |
| 403 | HTTPForbidden | heatclient/exc.py:111 |
| 404 | HTTPNotFound | the status commands catch most: § 3 |
| 405 | HTTPMethodNotAllowed | heatclient/exc.py:128 |
| 409 | HTTPConflict | shown as is [observed, E10] |
| 413 | HTTPOverLimit | heatclient/exc.py:146 |
| 415 | HTTPUnsupported | heatclient/exc.py:150 |
| 500, 501, 502, 503 | HTTPInternalServerError, HTTPNotImplemented, HTTPBadGateway, HTTPServiceUnavailable | heatclient/exc.py:154-171 |
| any other 4xx or 5xx | HTTPException with that code | [observed, E07 with 422] |
| 301, 302, 303, 305, 307, 308 | not an error | followed by keystoneauth (keystoneauth1:keystoneauth1/session.py:385), except for the lookup, which sets redirect=False; the re-issue at heatclient/common/http.py:323-327 only runs if a caller passes redirect=True, and none does |
Network errors come from keystoneauth, not from this package [inferred: heatclient.exc.CommunicationError is raised only by the older HTTPClient, heatclient/common/http.py:219-223].
Error body to message
HTTPException.__init__ parses the body, and __str__ prints ERROR: <error.message> (heatclient/exc.py:48-73). Heat's fault body is {"code", "title", "explanation", "error": {"type", "traceback", "message"}}, with message present only for safe exceptions (heat:heat/api/middleware/fault.py:152-164).
| Body received | Message shown | Run |
|---|---|---|
{"error": {"message": "m", …}} | ERROR: m | [observed, E01] |
{"error": {"type": …}}, no message | ERROR: Internal Error | [observed, E02] |
JSON without error | ERROR: None: the fallback is the class docstring, and HTTPNotFound has none | [observed, E03] |
| empty | ERROR: None | [observed, E06] |
| not JSON | ERROR: b'<html>…': the bytes literal | [observed, E04] |
{"error": "text"} | a logging traceback on stderr, because __str__ itself raises; exit 1 | [observed, E05] |
The legacy shell's --verbose sets exc.verbose, which adds error.traceback to the message (heatclient/exc.py:68-71, heatclient/shell.py:394-396). OSC never sets it [inferred: no reference in heatclient/osc].
Two CommandError classes
heatclient.exc.CommandError and osc_lib.exceptions.CommandError are unrelated. Both print their message and exit 1. It matters in two places: stack delete --wait catches only the heatclient one (heatclient/osc/v1/stack.py:784, heatclient/osc/v1/stack.py:804), and heatclient/osc/v1/event.py:25 and heatclient/osc/v1/template.py:26 import heatclient.exc as exc, unlike the other modules, where exc is osc-lib's.
3. Which 404 gives which text
Most commands turn a 404 into their own text, which hides Heat's message. All of these were [observed] in the 404 probe.
| Text | Commands |
|---|---|
Stack not found: <x> | stack show, abandon, export, environment show, file list, template show, suspend, resume, check, cancel, output list, resource list, snapshot create, snapshot list, hook poll, hook clear without a type |
Heat's message, as ERROR: … | stack create, adopt, update, list, failures list, event list, event show, hook clear with a type, orchestration template validate, build info, service list, template version list, resource type list, software config create, software config list, software deployment list, software deployment metadata show |
Stack or resource not found: <x> <r>, or Stack <x> or resource <r> not found. | stack resource show, mark unhealthy; stack resource metadata, signal |
Stack <x> or output <o> not found. | stack output show after its fallback |
Snapshot ID <s> not found for stack <x>, Stack <x> or snapshot <s> not found. | stack snapshot show, delete; restore |
Resource type not found: <t>, Template version not found: <v> | orchestration resource type show, template function list |
Configuration not found: <c>, Software configuration not found: <c> | software config show, software deployment create --config |
Software Deployment not found: <d>, Deployment not found: <d> | software deployment show, output show |
lines on stdout, then Unable to delete N of the M … | stack delete, software config delete, software deployment delete |
4. Defects
All were found by reading the code and reproduced on the stub, unless the row says otherwise. Each reproduction is a scenario with the exact argv, the bytes answered and the requests.
**1. software deployment create fails with its default options.** 'NoneType' object has no attribute 'get_token', exit 1, no request sent [observed, D01].
- Cause: the default
--signal-transportisTEMP_URL_SIGNAL(heatclient/osc/v1/software_deployment.py:67), sobuild_signal_idcallsauth.get_tokenonhc.http_client.auth(heatclient/common/deployment_utils.py:128-135), and the plugin passes noauth(heatclient/osc/plugin.py:41-43). - Even with an auth plugin, the next lines read
args.os_region_name,os_project_name,os_tenant_name,os_username,os_cacertandinsecure(heatclient/common/deployment_utils.py:138-149), which the OSC command parser does not define [inferred: not reached]. - Tests do not see it, because they mock
build_signal_id(heatclient/tests/unit/osc/v1/test_software_deployment.py:120). heat deployment-createworks on the same stub:PUTcontainer,HEADaccount,PUTobject, then the two Heat calls [observed, L02].- Workaround:
--signal-transport NO_SIGNAL[observed, G20].
**2. stack list --long and --all-projects do not do what the help says.**
- The help says
--longis "implied by --all-projects" (heatclient/osc/v1/stack.py:574-575). The code does the reverse:--longsendsglobal_tenant=True(heatclient/osc/v1/stack.py:674) [observed, D22], and--all-projectsadds no column (heatclient/osc/v1/stack.py:695-696) [observed, D23]. - Heat routes
global_tenant=Truetoglobal_index, denied to everybody by default (heat:heat/api/openstack/v1/stacks.py:353-358,heat:heat/policies/stacks.py:283-284). So on a default Heat, wanting more columns gives a 403 [inferred: Heat read, not run; the stub's 403 was scripted].
**3. stack hook poll crashes when the start event is not found.** list index out of range, at heatclient/common/event_utils.py:47-49.
- With a stack ID, which the help allows: events carry the stack name from their link, compared with the ID (
heatclient/common/event_utils.py:46-49,heatclient/common/event_utils.py:175) [observed, D02]. - With a name, when no
Stack CREATE startedevent exists [observed, D03].
**4. orchestration template version list needs at least 2 versions.** It reads versions[1].aliases and catches only AttributeError (heatclient/osc/v1/template.py:45-52). One or zero versions give list index out of range [observed, D04]. Harmless on Heat, which ships many; a concern for a trimmed rewrite.
**5. stack adopt --timeout is sent under the wrong key.** The body holds "timeout": 7 (heatclient/osc/v1/stack.py:887) [observed, D05], and null without the option. stack create and the legacy adopt send timeout_mins (heatclient/osc/v1/stack.py:179, heatclient/v1/shell.py:213), the key Heat reads (heat:heat/rpc/api.py:25, heat:heat/api/openstack/v1/stacks.py:390). So the option has no effect on a real Heat [inferred: Heat read, not run].
**6. software deployment output show <id> <name> --long still truncates.** long is not passed for a named output (heatclient/osc/v1/software_deployment.py:355-356), only for --all (heatclient/osc/v1/software_deployment.py:344-345). The output even ends with (truncated, view all with --long) [observed, D06, and D06b for --all --long].
7. Lazy loading fails on the session client.
Resource.getreadsself.manager.client.last_request_id(heatclient/common/base.py:486-487), which onlyHTTPClientdefines (heatclient/common/http.py:103). So when a response from agetlacks an attribute the code reads, the client sends a secondGET, then fails:'SessionClient' object has no attribute 'last_request_id'[observed, D08].- In
software deployment delete, thatAttributeErrorlands in anexceptthat formatsconfig_id, not yet assigned:UnboundLocalError(heatclient/osc/v1/software_deployment.py:167-173). By then the deployment is already deleted, and the other deployments on the command line are skipped [observed, D07]. If an earlier deployment set it, the message would name the wrong config [read in the code]. - Through
getattr(item, field, '')in osc-lib (osc-lib:osc_lib/utils/__init__.py:538), the failure is swallowed, leaving an extra request [inferred].
**8. stack delete --wait can report failure on a deleted stack.** After 2 empty event rounds, hc.stacks.get may answer 404. That raises HTTPNotFound, while the caller catches only heat_exc.CommandError (heatclient/common/event_utils.py:231, heatclient/osc/v1/stack.py:804) [observed, D09]. On a real Heat this needs events to answer 200 and the stack to vanish right after, a narrow window [inferred].
9. Help and messages that do not match the code.
software config create: the help of--config-fileand--definition-fileis swapped, and the metavar is<destination-file>(heatclient/osc/v1/software_config.py:123-133). The code reads the definition as the map and the config as the script (heatclient/osc/v1/software_config.py:155-168) [observed, G22], as the legacy help says (heatclient/v1/shell.py:1286-1290).software deployment create: the help documentsZAQAR_SIGNAL(heatclient/osc/v1/software_deployment.py:70-72), refused withUnsupported signal transport ZAQAR_SIGNAL(heatclient/common/deployment_utils.py:80-82) [observed, wire probe variant].stack updatewithout template: the message names--template-file,--template-urland--template-object, absent from OSC (heatclient/common/template_utils.py:80-87) [observed, wire probe].--parameterand--parameter-filehelp onstack updatespeak of creating the stack (heatclient/osc/v1/stack.py:273-274,heatclient/osc/v1/stack.py:279-281) [read in the code].
**10. Silent success or raw error in stack hook clear.**
- No resource matches the pattern: nothing is done, exit 0 [observed, D15].
- The signal answers 404: an error line in the log, exit 0 (
heatclient/common/hook_utils.py:28-33) [observed, D16]. stack hook clear x /:list index out of range(heatclient/osc/v1/stack.py:1465-1466) [observed, D14].
Similarly, software deployment output show <id> <unknown> prints nothing and exits 0 when the deployment has no outputs, because the check sits inside if outputs: (heatclient/osc/v1/software_deployment.py:340) [observed, D27].
11. Bad local input gives raw Python errors.
| Input | Message | At | Run |
|---|---|---|---|
| template holding a YAML integer | argument of type 'int' is not iterable | heatclient/common/template_format.py:78 | [observed, D17] |
environment with resource_registry: {a: null} | argument of type 'NoneType' is not iterable | heatclient/common/template_utils.py:360 | [observed, D18] |
--definition-file holding a scalar | 'str' object has no attribute 'get' | heatclient/osc/v1/software_config.py:161 | [observed, D19] |
--parameter-file k=a --parameter-file k=b | Cannot mix str and non-str arguments | heatclient/common/utils.py:385 | [observed, D20] |
missing --data-file | <urlopen error [Errno 2] No such file or directory: '…'> | heatclient/osc/v1/resource.py:238 | [observed, E11] |
12. Response shapes the client does not check. With 200 {} on every route, 32 of the 49 commands exit 1, 28 of them with a raw Python error, one of which is defect 1 [observed, wire-probe-200]. A compatible service must therefore send the keys of 07 § 2.
| Failure | Printed | At | Commands, for example |
|---|---|---|---|
| list key missing | 'stacks', 'resources', 'events'… | heatclient/common/base.py:118 | stack list, stack resource list, software config list |
| object key missing | 'NoneType' object has no attribute 'items' | heatclient/common/base.py:453 | stack show, stack resource show, software config show |
stack missing on create or adopt | 'stack' | heatclient/osc/v1/stack.py:205, heatclient/osc/v1/stack.py:890 | stack create, stack adopt |
stack.id missing | 'id' | heatclient/osc/v1/stack.py:213 | [D13] |
outputs, output, snapshots missing | 'outputs', 'output', 'snapshots' | heatclient/osc/v1/stack.py:1089, heatclient/osc/v1/stack.py:1045, heatclient/osc/v1/snapshot.py:66 | |
metadata missing | 'NoneType' object has no attribute 'values' | heatclient/osc/v1/resource.py:186 | stack resource metadata |
| empty deployment | not enough values to unpack (expected 2, got 0), from the zip at heatclient/osc/v1/software_deployment.py:130 | [D26] | |
lookup with no Location | Location not returned with redirect | heatclient/v1/stacks.py:115 | [D11] |
Location without /stacks/ | list index out of range | heatclient/v1/stacks.py:116 | [D10] |
| 200 not JSON on a list | Expecting value: line 1 column 1 (char 0) | heatclient/common/base.py:113 | [D25] |
| 200 not JSON elsewhere | get_response_body gives None (heatclient/common/utils.py:436-445): stack template show prints nothing, exit 0 | [D24] |
13. What a template can make the client do. Both follow from design, and both were seen with hand-written templates.
- The token can leave for a host named in a template [observed, T01].
-t <url>is first fetched anonymously. OnURLError, including an HTTP 401 or 404, the same URL goes through the authenticated session (heatclient/common/template_utils.py:48-53,heatclient/common/http.py:43-46). From there,is_objectis true, so everyget_fileand templatetypein that template, any absolute URL, is fetched the same way (heatclient/common/template_utils.py:163-164). The stub's second port receivedX-Auth-Token. It takes a URL that refuses anonymous access, then an attacker able to change that template. - A remote template can read local files [observed, T02]. A template fetched over HTTP with
get_file: file:///pathmakes the client read that local file and send it infilesto Heat (heatclient/common/template_utils.py:161-166,heatclient/common/utils.py:410-412): no check that the scheme matches the template's. The content goes to the user's own Heat, and then to whoever can read that stack's files [inferred].
5. Client-side messages reachable from each command
Generated from _rewrite/cli-commands_stable-2026.2.json: 45 message templates at 73 raise sites, following calls inside the package. %s values are filled at run time.
| Message (template) | Exception | Raised at | Commands |
|---|---|---|---|
| (message built at run time) | CommandError, ValueError, template_file_exc | heatclient/common/environment_format.py:45, heatclient/common/event_utils.py:170, heatclient/common/template_format.py:73, heatclient/common/template_utils.py:56 (+6) | orchestration template validate, stack abandon, stack adopt (+11) |
| --follow can only be specified with --format log | CommandError | heatclient/osc/v1/event.py:196 | stack event list |
| --nested-depth cannot be specified with --resource | CommandError | heatclient/osc/v1/event.py:171 | stack event list |
| --nested-depth invalid value %s | CommandError | heatclient/osc/v1/stack.py:1383 | stack hook poll |
| Cannot specify both <OUTPUT NAME> and --all. | CommandError | heatclient/osc/v1/stack.py:1026 | stack output show |
| Cannot use --os-no-client-auth, auth required to create a Swift TempURL. | CommandError | heatclient/common/deployment_utils.py:125 | software deployment create |
| Cannot use --template-type and --long in one time. | CommandError | heatclient/osc/v1/resource_type.py:57 | orchestration resource type show |
| Configuration not found: %s | CommandError | heatclient/osc/v1/software_config.py:220 | software config show |
| Could not fetch contents for %s | CommandError | heatclient/common/utils.py:414 | orchestration template validate, stack adopt, stack create (+1) |
| Could not fetch template from %s | CommandError | heatclient/common/template_utils.py:90 | orchestration template validate, stack adopt, stack create (+1) |
| Data should be a JSON dict | CommandError | heatclient/osc/v1/resource.py:246 | stack resource signal |
| Data should be in JSON format: %s | CommandError | heatclient/osc/v1/resource.py:244 | stack resource signal |
| Deployment not found: %s | CommandError | heatclient/osc/v1/software_deployment.py:337 | software deployment output show |
| Either <OUTPUT NAME> or --all must be specified. | CommandError | heatclient/osc/v1/stack.py:1022 | stack output show |
| environment has wrong section "%s" | ValueError | heatclient/common/environment_format.py:55 | orchestration template validate, stack adopt, stack create (+1) |
| Error parsing template %(url)s %(error)s | CommandError | heatclient/common/template_utils.py:98 | orchestration template validate, stack adopt, stack create (+1) |
| Error: either %(output)s or %(all)s argument is needed. | CommandError | heatclient/osc/v1/software_deployment.py:330 | software deployment output show |
| Malformed parameter(%s). Use the key=value format. | CommandError | heatclient/common/utils.py:340 | orchestration resource type list, orchestration template validate, software deployment create (+6) |
| Need to specify exactly one of [%(arg1)s, %(arg2)s or %(arg3)s] or %(arg4)s | CommandError | heatclient/common/template_utils.py:80 | orchestration template validate, stack adopt, stack create (+1) |
| Output %(output)s does not exist in deployment %(deployment)s | CommandError | heatclient/osc/v1/software_deployment.py:352 | software deployment output show |
| Output error: %s | CommandError | heatclient/osc/v1/stack.py:1064 | stack output show |
| Resource type not found: %s | CommandError | heatclient/osc/v1/resource_type.py:78 | orchestration resource type show |
| Should only specify one of data or data-file | CommandError | heatclient/osc/v1/resource.py:233 | stack resource signal |
| Snapshot ID <%(snapshot_id)s> not found for stack <%(stack_id)s> | CommandError | heatclient/osc/v1/snapshot.py:235, heatclient/osc/v1/snapshot.py:99 | stack snapshot delete, stack snapshot show |
| Software configuration not found: %s | CommandError | heatclient/osc/v1/software_deployment.py:110 | software deployment create |
| Software Deployment not found: %s | CommandError | heatclient/osc/v1/software_deployment.py:249 | software deployment show |
| Stack %(id)s or output %(out)s not found. | CommandError | heatclient/osc/v1/stack.py:1058, heatclient/osc/v1/stack.py:1060 | stack output show |
| Stack %(id)s with status '%(status)s' not in cancelable state | CommandError | heatclient/osc/v1/stack.py:1330 | stack cancel |
| Stack %(stack)s or resource %(resource)s not found. | CommandError | heatclient/osc/v1/resource.py:181, heatclient/osc/v1/resource.py:252 | stack resource metadata, stack resource signal |
| Stack %(stack)s or snapshot %(snapshot)s not found. | CommandError | heatclient/osc/v1/snapshot.py:139 | stack snapshot restore |
| Stack not found: %s | CommandError | heatclient/common/event_utils.py:129, heatclient/common/hook_utils.py:63, heatclient/osc/v1/resource.py:128, heatclient/osc/v1/snapshot.py:172 (+13) | stack abandon, stack adopt, stack cancel (+19) |
| Stack or resource not found: %(id)s %(resource)s | CommandError | heatclient/osc/v1/resource.py:300 | stack resource mark unhealthy |
| Stack or resource not found: %(stack)s %(resource)s | CommandError | heatclient/osc/v1/resource.py:68 | stack resource show |
| Stack status %s not IN_PROGRESS | CommandError | heatclient/common/hook_utils.py:66 | stack hook clear, stack hook poll |
| tag mode must be one of "any", "not", "not-any" | CommandError | heatclient/osc/v1/stack.py:688 | stack list |
| Template format version not found. | ValueError | heatclient/common/template_format.py:81 | orchestration template validate, stack adopt, stack create (+1) |
| Template type invalid: %s | CommandError | heatclient/osc/v1/resource_type.py:68 | orchestration resource type show |
| Template version not found: %s | CommandError | heatclient/osc/v1/template.py:93 | orchestration template function list |
| The environment is not a valid YAML mapping data type. | ValueError | heatclient/common/environment_format.py:50 | orchestration template validate, stack adopt, stack create (+1) |
| Unable to delete %(count)d of the %(total)d stacks. | CommandError | heatclient/osc/v1/stack.py:813 | stack delete |
| Unable to delete %(count)s of the %(total)s deployments. | CommandError | heatclient/osc/v1/software_deployment.py:176 | software deployment delete |
| Unable to delete %(count)s of the %(total)s software configs. | CommandError | heatclient/osc/v1/software_config.py:67 | software config delete |
| Unexpected hook type %s | CommandError | heatclient/common/event_utils.py:39 | stack hook poll |
| Unexpected stack status %s, only create, update and delete supported | CommandError | heatclient/common/hook_utils.py:76 | stack hook clear, stack hook poll |
| Unsupported signal transport %s | CommandError | heatclient/common/deployment_utils.py:81 | software deployment create |
Source: reverse-spec-python-heatclient/02-errors_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.