OpenKeel: the sovereign French Cloud solution of the inherent group, with a European reach Discover inherent

Reverse-spec · Clients

python-heatclient — Errors

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

CodeSituationSource
0the command returned; also several silent no-ops listed in § 4cliff:cliff/command.py:196
1any exception during the command: CommandError, HTTP*, or a raw KeyError, IndexError, AttributeError. The text alone goes to stderr, with no traceback unless --debugcliff:cliff/app.py:462-467
2argument 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).

StatusClassNote
300HTTPMultipleChoicesfixed text: HTTPMultipleChoices (HTTP 300) Requested version of Heat API is not available. [observed, E08]
400HTTPBadRequestheatclient/exc.py:93
401HTTPUnauthorizedkeystoneauth first re-authenticates and retries once (keystoneauth1:keystoneauth1/session.py:1130-1131) [observed, E09: 2 authentications, 2 requests]
403HTTPForbiddenheatclient/exc.py:111
404HTTPNotFoundthe status commands catch most: § 3
405HTTPMethodNotAllowedheatclient/exc.py:128
409HTTPConflictshown as is [observed, E10]
413HTTPOverLimitheatclient/exc.py:146
415HTTPUnsupportedheatclient/exc.py:150
500, 501, 502, 503HTTPInternalServerError, HTTPNotImplemented, HTTPBadGateway, HTTPServiceUnavailableheatclient/exc.py:154-171
any other 4xx or 5xxHTTPException with that code[observed, E07 with 422]
301, 302, 303, 305, 307, 308not an errorfollowed 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 receivedMessage shownRun
{"error": {"message": "m", …}}ERROR: m[observed, E01]
{"error": {"type": …}}, no messageERROR: Internal Error[observed, E02]
JSON without errorERROR: None: the fallback is the class docstring, and HTTPNotFound has none[observed, E03]
emptyERROR: None[observed, E06]
not JSONERROR: 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.

TextCommands
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-transport is TEMP_URL_SIGNAL (heatclient/osc/v1/software_deployment.py:67), so build_signal_id calls auth.get_token on hc.http_client.auth (heatclient/common/deployment_utils.py:128-135), and the plugin passes no auth (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_cacert and insecure (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-create works on the same stub: PUT container, HEAD account, PUT object, 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 --long is "implied by --all-projects" (heatclient/osc/v1/stack.py:574-575). The code does the reverse: --long sends global_tenant=True (heatclient/osc/v1/stack.py:674) [observed, D22], and --all-projects adds no column (heatclient/osc/v1/stack.py:695-696) [observed, D23].
  • Heat routes global_tenant=True to global_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 started event 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.get reads self.manager.client.last_request_id (heatclient/common/base.py:486-487), which only HTTPClient defines (heatclient/common/http.py:103). So when a response from a get lacks an attribute the code reads, the client sends a second GET, then fails: 'SessionClient' object has no attribute 'last_request_id' [observed, D08].
  • In software deployment delete, that AttributeError lands in an except that formats config_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-file and --definition-file is 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 documents ZAQAR_SIGNAL (heatclient/osc/v1/software_deployment.py:70-72), refused with Unsupported signal transport ZAQAR_SIGNAL (heatclient/common/deployment_utils.py:80-82) [observed, wire probe variant].
  • stack update without template: the message names --template-file, --template-url and --template-object, absent from OSC (heatclient/common/template_utils.py:80-87) [observed, wire probe].
  • --parameter and --parameter-file help on stack update speak 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.

InputMessageAtRun
template holding a YAML integerargument of type 'int' is not iterableheatclient/common/template_format.py:78[observed, D17]
environment with resource_registry: {a: null}argument of type 'NoneType' is not iterableheatclient/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=bCannot mix str and non-str argumentsheatclient/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.

FailurePrintedAtCommands, for example
list key missing'stacks', 'resources', 'events'…heatclient/common/base.py:118stack list, stack resource list, software config list
object key missing'NoneType' object has no attribute 'items'heatclient/common/base.py:453stack 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:890stack 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:186stack resource metadata
empty deploymentnot enough values to unpack (expected 2, got 0), from the zip at heatclient/osc/v1/software_deployment.py:130[D26]
lookup with no LocationLocation not returned with redirectheatclient/v1/stacks.py:115[D11]
Location without /stacks/list index out of rangeheatclient/v1/stacks.py:116[D10]
200 not JSON on a listExpecting value: line 1 column 1 (char 0)heatclient/common/base.py:113[D25]
200 not JSON elsewhereget_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. On URLError, 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_object is true, so every get_file and template type in that template, any absolute URL, is fetched the same way (heatclient/common/template_utils.py:163-164). The stub's second port received X-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:///path makes the client read that local file and send it in files to 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)ExceptionRaised atCommands
(message built at run time)CommandError, ValueError, template_file_excheatclient/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 logCommandErrorheatclient/osc/v1/event.py:196stack event list
--nested-depth cannot be specified with --resourceCommandErrorheatclient/osc/v1/event.py:171stack event list
--nested-depth invalid value %sCommandErrorheatclient/osc/v1/stack.py:1383stack hook poll
Cannot specify both <OUTPUT NAME> and --all.CommandErrorheatclient/osc/v1/stack.py:1026stack output show
Cannot use --os-no-client-auth, auth required to create a Swift TempURL.CommandErrorheatclient/common/deployment_utils.py:125software deployment create
Cannot use --template-type and --long in one time.CommandErrorheatclient/osc/v1/resource_type.py:57orchestration resource type show
Configuration not found: %sCommandErrorheatclient/osc/v1/software_config.py:220software config show
Could not fetch contents for %sCommandErrorheatclient/common/utils.py:414orchestration template validate, stack adopt, stack create (+1)
Could not fetch template from %sCommandErrorheatclient/common/template_utils.py:90orchestration template validate, stack adopt, stack create (+1)
Data should be a JSON dictCommandErrorheatclient/osc/v1/resource.py:246stack resource signal
Data should be in JSON format: %sCommandErrorheatclient/osc/v1/resource.py:244stack resource signal
Deployment not found: %sCommandErrorheatclient/osc/v1/software_deployment.py:337software deployment output show
Either <OUTPUT NAME> or --all must be specified.CommandErrorheatclient/osc/v1/stack.py:1022stack output show
environment has wrong section "%s"ValueErrorheatclient/common/environment_format.py:55orchestration template validate, stack adopt, stack create (+1)
Error parsing template %(url)s %(error)sCommandErrorheatclient/common/template_utils.py:98orchestration template validate, stack adopt, stack create (+1)
Error: either %(output)s or %(all)s argument is needed.CommandErrorheatclient/osc/v1/software_deployment.py:330software deployment output show
Malformed parameter(%s). Use the key=value format.CommandErrorheatclient/common/utils.py:340orchestration 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)sCommandErrorheatclient/common/template_utils.py:80orchestration template validate, stack adopt, stack create (+1)
Output %(output)s does not exist in deployment %(deployment)sCommandErrorheatclient/osc/v1/software_deployment.py:352software deployment output show
Output error: %sCommandErrorheatclient/osc/v1/stack.py:1064stack output show
Resource type not found: %sCommandErrorheatclient/osc/v1/resource_type.py:78orchestration resource type show
Should only specify one of data or data-fileCommandErrorheatclient/osc/v1/resource.py:233stack resource signal
Snapshot ID <%(snapshot_id)s> not found for stack <%(stack_id)s>CommandErrorheatclient/osc/v1/snapshot.py:235, heatclient/osc/v1/snapshot.py:99stack snapshot delete, stack snapshot show
Software configuration not found: %sCommandErrorheatclient/osc/v1/software_deployment.py:110software deployment create
Software Deployment not found: %sCommandErrorheatclient/osc/v1/software_deployment.py:249software deployment show
Stack %(id)s or output %(out)s not found.CommandErrorheatclient/osc/v1/stack.py:1058, heatclient/osc/v1/stack.py:1060stack output show
Stack %(id)s with status '%(status)s' not in cancelable stateCommandErrorheatclient/osc/v1/stack.py:1330stack cancel
Stack %(stack)s or resource %(resource)s not found.CommandErrorheatclient/osc/v1/resource.py:181, heatclient/osc/v1/resource.py:252stack resource metadata, stack resource signal
Stack %(stack)s or snapshot %(snapshot)s not found.CommandErrorheatclient/osc/v1/snapshot.py:139stack snapshot restore
Stack not found: %sCommandErrorheatclient/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)sCommandErrorheatclient/osc/v1/resource.py:300stack resource mark unhealthy
Stack or resource not found: %(stack)s %(resource)sCommandErrorheatclient/osc/v1/resource.py:68stack resource show
Stack status %s not IN_PROGRESSCommandErrorheatclient/common/hook_utils.py:66stack hook clear, stack hook poll
tag mode must be one of "any", "not", "not-any"CommandErrorheatclient/osc/v1/stack.py:688stack list
Template format version not found.ValueErrorheatclient/common/template_format.py:81orchestration template validate, stack adopt, stack create (+1)
Template type invalid: %sCommandErrorheatclient/osc/v1/resource_type.py:68orchestration resource type show
Template version not found: %sCommandErrorheatclient/osc/v1/template.py:93orchestration template function list
The environment is not a valid YAML mapping data type.ValueErrorheatclient/common/environment_format.py:50orchestration template validate, stack adopt, stack create (+1)
Unable to delete %(count)d of the %(total)d stacks.CommandErrorheatclient/osc/v1/stack.py:813stack delete
Unable to delete %(count)s of the %(total)s deployments.CommandErrorheatclient/osc/v1/software_deployment.py:176software deployment delete
Unable to delete %(count)s of the %(total)s software configs.CommandErrorheatclient/osc/v1/software_config.py:67software config delete
Unexpected hook type %sCommandErrorheatclient/common/event_utils.py:39stack hook poll
Unexpected stack status %s, only create, update and delete supportedCommandErrorheatclient/common/hook_utils.py:76stack hook clear, stack hook poll
Unsupported signal transport %sCommandErrorheatclient/common/deployment_utils.py:81software deployment create

Source: reverse-spec-python-heatclient/02-errors_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.