a tool row with no cmd no longer execs the utterance (V-581)

An enabled row whose Cmd is empty built argv from the spoken args alone. So
args[0] became the program name, and free text picked the binary. A proposal is
drafted with no cmd. /tools can enable one before anybody fills it in, so
reaching this took no compromise. Exec now refuses such a row with ErrNotEnabled
before it builds argv. TestExecEmptyCmdRefuses pins it.

Three smaller reads in the same sweep. CapabilityOf parsed a Home Assistant
entity id by hand where smarthome.DomainOf already does it. The fallback for an
id with no dot is unchanged. GroupByDomain built its map key twice per row. The
zenmoney and Home Assistant HTTP clients read an error body before checking the
status that discards it. The status check moved ahead of the read in both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-06 03:11:53 +04:00
parent 316fb197a8
commit f4a021d3da
5 changed files with 47 additions and 16 deletions
+6 -5
View File
@@ -295,14 +295,15 @@ func (c *Client) do(ctx context.Context, method, path string, body []byte) ([]by
return nil, fmt.Errorf("smarthome: %s %s: %w", method, path, err)
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
// The status only, and read before the body: an error page can carry the
// instance's own detail, and there is no reason to pull it into memory to
// discard it.
return nil, fmt.Errorf("smarthome: %s %s: http %d", method, path, resp.StatusCode)
}
out, err := io.ReadAll(io.LimitReader(resp.Body, maxBody))
if err != nil {
return nil, fmt.Errorf("smarthome: read %s: %w", path, err)
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
// The body of an error can contain the instance's own detail; the token
// never appears in it, but keep it to one line anyway.
return nil, fmt.Errorf("smarthome: %s %s: http %d", method, path, resp.StatusCode)
}
return out, nil
}