Releases: vmware-skills/VMware-AIops
Release list
v1.8.22 — a dropped connection no longer keeps itself alive
Every connect() registered an atexit cleanup that closes over the
ServiceInstance, and atexit held that closure — and so the SI, its stub and
its socket — until the process exited. Nothing ever unregistered it. A
long-running MCP server that reconnects after each session expiry therefore
pinned one dead connection per reconnect, and at exit would run a Disconnect
against every session it had ever opened.
Measured before the fix: 20 evict-and-reconnect cycles left all 20 evicted
ServiceInstance objects reachable. The id(si) side stores were correctly down
to one entry throughout — the side-store discipline was never the leak, the
registration was.
_release_si() now takes the handler back off at both points that drop a
connection: the eviction inside connect() and disconnect(). Five repos had
the identical shape, so family_smoke gained a gate for it (154 → 155).
Not WeakKeyDictionary, which looks like the obvious fix and is a regression:
pyVmomi's ManagedObject.__eq__ compares moId, class and serverGuid, and every
ServiceInstance carries moId 'ServiceInstance' with serverGuid None. Two
vCenters collapse into one entry — connecting to the second silently hands the
first one's verify_ssl to both. Keying by id() is right precisely because
it is identity.
v1.8.21 — snapshot-delete gains the flag its docs already promised
The reference documented vm snapshot-delete <name> --name <snap> [--remove-children]. The option did not exist, so following the documentation
got "No such option" — while the capability was real all along: the ops layer
takes remove_children and the MCP tool exposes it. Only the CLI never passed
it through. It reaches the --dry-run preview too, so the difference is visible
before the delete rather than after.
v1.8.20 — `vm_cancel_ttl` is not destructive — it prevents a deletion
The annotation was copied from vm_set_ttl directly above it, where
destructiveHint=True is right and carefully argued: set_ttl schedules an
unattended deletion that a daemon carries out later. cancel_ttl calls that off.
No behaviour changes; the CLI already asked nothing, correctly. What changes is
that a client reading the annotations is no longer told that preventing a
deletion is a destructive act.
v1.8.19 — one answer per .env, on every platform
.env permissions are decided by vmware_policy.fsperms instead of POSIX mode
bits. On Windows a single command printed both a red "has permissions 0o666
(should be 600). Run: chmod 600" from this hot path — where chmod does nothing
— and a green "this platform does not express file permissions as POSIX mode
bits ... run: icacls" from doctor, about the same file in the same run. doctor
had been moved to the three-state check and the path that runs on every command
had not. An unmeasurable platform is now silent here rather than loudly wrong.
The vmware-monitor floor moves to 1.10.0: the investigation bundles this
skill re-exports had a cluster check that never matched on real hardware, and
the fix ships in Monitor.
v1.8.18
The suite now runs on a cp936 machine. Round 3 of the VCF 9 field testing ran
on Windows Server 2025 with locale cp936. Across the family four repos' suites --
1687 tests -- never executed at all, dying at collection reading our own UTF-8
sources, and 101 more failed the same way. Most of those were the tests that
verify the destructive-operation guardrails: the guardrails were fine, the tests
that check them could not open a file. On the UTF-8 CI every one of them was
green. A security test that cannot run is not a security test.
Every text read and write here names its encoding now, tests/ included -- the
previous round fixed only the package, which is why this came back. A gate in
family_smoke scans both trees by AST, and the whole family's suites were re-run
under an ASCII locale to confirm: 15 of 15 green, from 1 of 15.
--help no longer dies on a console that cannot encode it. On any console
whose encoding cannot carry the characters in our own help text, --help exited
with a UnicodeEncodeError traceback -- unavailable exactly on the machines
where it is most needed. Four repos were affected; the handler is now relaxed in
all fifteen so a glyph degrades instead of killing the command.
Its environment resolver no longer answers for other skills.
set_environment_resolver wrote one process-global slot and twelve servers
registered into it at import time, so the last one won for all of them --
measured taking a freeze-production-writes rule from DENY to ALLOW on another
skill's production target. Registration is keyed by skill now (requires
vmware-policy 1.12.0).
The .env permission check stopped being permanently red on Windows. It was
POSIX-only, and chmod 600 there exits 0 without changing any bits -- so
doctor printed a failure on every run with a remedy that could not clear it.
Three states now, via vmware_policy.fsperms: only a demonstrated exposure
fails, and "this platform cannot answer" says so and offers icacls.
Unknown tool arguments are refused instead of dropped. The schema declared
additionalProperties: false and the runtime accepted them anyway, so a filter
argument whose name a model guessed wrong returned the unfiltered result with
nothing to indicate anything had been discarded. Fixed in vmware-policy 1.12.0
and in force here.
Requires vmware-policy 1.12.0.
v1.8.17 — closed-value tool parameters reach the MCP schema
drs_behavior (cluster_create, cluster_configure), rule_type (create_drs_rule) and binding (create_dvs_portgroup) were typed str, with their valid values named only in prose. An MCP client sees the schema, not the prose, so a model had to guess the spelling.
They are now Literal[...], and a regression test pins each enum against the set the ops layer actually validates against (_VALID_DRS_BEHAVIORS, _CREATABLE_RULE_TYPES, _VALID_BINDINGS), so the two cannot drift apart.
v1.8.16 — three MCP annotations that were lying, and errors invisible on the wire
vm_guest_download claimed readOnlyHint: true while overwriting an arbitrary
caller-supplied path — and that hint is what a client consults before asking for
confirmation, so the annotation was a safety control that silently did not
apply. It refuses an occupied destination now unless overwrite=true, refuses
directories, and refuses symlinks even with it. vm_set_ttl claimed
destructive: false while scheduling an unattended auto-delete.
vm_create_snapshot claimed idempotent: true while calling
CreateSnapshot_Task unconditionally — the field a client reads before
retrying, and this family retries transient failures once.
Exceptions were caught inside tools and returned as ordinary values, so
isError was always false: over stdio a client could not tell "the target does
not exist" from "succeeded". The frame is marked now, with the authored teaching
message intact.
The documentation claimed double confirmation with no bypass on a surface that
has neither. The git history and the design record show that removal was a
decision, not an accident — vm_delete never had a confirmation parameter — so
the documents were corrected rather than the code, and each now names which
surface it is describing.
The vmware-policy floor moves to >=1.11.0. Policy 1.11.0 stops the engine
failing open: on a host whose locale is not UTF-8, reading rules.yaml raised a
decode error that was swallowed, and a freeze-production-writes rule came back
ALLOW. No new API is used here, so the floor could have stayed — it is raised
because leaving it low means a user resolving 1.10.0 keeps the permissive engine
and the fix never reaches them. One behaviour travels with it: on a host whose
rules file cannot be read, operations move from all-allowed to all-denied.
VMWARE_POLICY_DISABLED=1 is checked above the rules, so the escape hatch does
not itself depend on them loading.
Also in this release: the suite no longer appends to the operator's real
~/.vmware/audit.db. It held over 30,000 rows dominated by tool names nobody
had invoked, including 1,400 entries for a destructive operation that never
happened — an audit trail carrying test fiction cannot answer the question it is
kept for.
v1.8.15 — three annotations that were lying, and errors invisible on the wire
Parameter descriptions reach the JSON schema for the first time. An MCP client
sees the schema, not the docstring, and this repo's coverage of description
and additionalProperties was 0% — while nearly every parameter was already
described in an Args: block no client ever receives.
Measured on a real VCF 9.1 estate, the gap produced a silent failure with no
error at any stage: a parameter name guessed wrong is discarded and the tool
returns the full unfiltered result; a value guessed wrong (power_state= "running") returns 0 rows where there were 11.
vmware-policy 1.10.0's describe_tool_parameters copies what is already
written, so the docstring is now load-bearing and the two cannot drift apart. It
removes the Args: block from the description once copied — both travel in
every tools/list response, so leaving it bills the same sentences twice
against the manifest's token budget. additionalProperties is closed: an open
schema is room for a model to invent arguments that are then silently
discarded, which is the other half of the same failure.
The vmware-policy floor moves to >=1.10.0. Older releases have no
describe_tool_parameters, and resolving one gives an ImportError at server
start rather than a missing feature.
Also in this release: three MCP annotations that were lying, and errors that
were invisible at the protocol level.
vm_guest_download claimed readOnlyHint: true while overwriting an arbitrary
caller-supplied local path — and readOnlyHint is what a client consults to
decide whether a call needs confirmation, so the annotation was a safety control
that silently did not apply. It is now a write, and it refuses an occupied
destination unless overwrite=true, refuses directories, and refuses symlinks
even with overwrite=true, because consenting to replace a path is not
consenting to replace what it points at. vm_set_ttl claimed
destructive: false while scheduling an unattended auto-delete.
vm_create_snapshot claimed idempotent: true while calling
CreateSnapshot_Task unconditionally — that is the field a client reads before
retrying, and this family retries transient failures once, so a timeout on a
snapshot that had in fact succeeded would be retried into a second delta-disk
chain.
Correcting the first one disarmed a second control by itself: the guarded-CLI
test derives its write set from readOnlyHint, so vm guest-download had been
the one file-writing command with no @guarded. It has one now.
And exceptions were caught inside tools and returned as ordinary values, so
isError was always false: over stdio a client could not tell "the target does
not exist" from "succeeded". The frame is now marked, with the authored
teaching message intact — verified in a live JSON-RPC session showing a failing
call and a succeeding one in the same transcript.
v1.8.14 — a host nobody could reach is not a host with no adapters
Found against a real VCF 9.1 estate where four of eight ESXi hosts were
notResponding. vCenter keeps answering for such a host out of its own cache,
with no error and no marker, so a read "succeeds" and looks authoritative.
list_host_vmks dropped unreachable hosts and still said the list was
complete. Asked for one, it raised a bare AttributeError; enumerating all
of them, it skipped the four silently and reported truncated: false — the
envelope positively certifying a list that was missing half the estate. Unread
hosts now appear as rows with reachable: false, null facts and the
connectionState in the note, and the envelope carries hosts_unreachable.
Three sibling write tools carried the identical bare dereference and were found
by grepping for the shape rather than waiting for them to be reported; they now
raise a teaching error, because reading the adapter list in order to change
it is a different question from listing it. The read and the writes deliberately
answer differently, and the reasoning is in the docstrings.
The distinction that makes it work: PropertyCollector omits a property it
could not read, so an absent config.network.vnic means "not read" while []
means "read, and empty". p.get(...) or [] collapsed the two, which is exactly
how four hosts disappeared.
doctor cleared an estate it had not checked, authenticating only the
default target — so five targets with three wrong passwords came back "All
checks passed", and the failure that followed told the user to run the doctor
that had just cleared them.
The CLI, the doctor and the MCP server opened different config files. With
VMWARE_AIOPS_CONFIG set, the server read that file while load_config() read
the default: the agent and the human on different vCenters, with the doctor
reporting on the human's. The precedence now lives in one resolve_config_path.
server.json never started the MCP server — it carried only the package
identifier, so a registry client composed uvx vmware-aiops, which runs the CLI
and exits.
The floor on vmware-monitor moves to 1.8.13. The cluster-health triage this
skill exposes is delegated to that package, and on anything older it reports
unreachable hosts as healthy ones.
v1.8.13 — list tools agents could not read
v1.8.13 — list tools that agents could not read, and a documented key the code ignores
- Three list tools answered in a shape the family contract does not use.
list_dvs_portgroupsreturned{total, returned, portgroups},
list_host_vmksreturned{total, returned, vmks}, and
scan_datastore_imagesreturned{images, last_scan}with no count at all.
Each is self-consistent; each is wrong, because an agent that has learned this
family readsitemsand gets nothing. The absent keys are the ones that
carry meaning:truncatedandhintexist because of issue #31, where a
model handed a bare list "incorrectly states that no data was returned". All
three now return the envelope, with the old keys kept as deprecated aliases so
nothing that reads them breaks. - Offset paging claimed there was more at the end of the list. Truncation
was derived fromreturned < total, which is wrong onceoffsetis
involved — the last page of five rows fetched withoffset=4returns one and
reportedtruncated: true, advising the caller to raise a limit that cannot
help them. Both offset-supporting tools now share one helper. - The SSL config key in every doc was one the code has never read. Docs said
disableSslCertValidation: true;config.pyreadsverify_ssl. Following the
instruction produced a config that is ignored.
Found by running against a real vCenter rather than a mock.