Skip to content

fix: four CLI and MCP bugs (#91, #74, #87, #73) - #92

Merged
jlrickert merged 5 commits into
mainfrom
fix/open-bugs-sept
Sep 2, 2026
Merged

fix: four CLI and MCP bugs (#91, #74, #87, #73)#92
jlrickert merged 5 commits into
mainfrom
fix/open-bugs-sept

Conversation

@jlrickert

Copy link
Copy Markdown
Owner

Four independent fixes, one commit each.

Integer frontmatter fields were unwritable (#91)

valueToYAMLNode tagged every integer and float !!str, so a schema field typed integer could not be satisfied through the MCP create or edit tools. A schema marking such a field required was impossible to satisfy at all: the node could not be created, and meta could not repair it afterwards because it validates the whole node including markdown sections.

Integers now tag !!int and floats !!float, matching the !!bool case that was already correct. Integral floats serialize as plain integers, because JSON has no integer type and a value sent over MCP as 1 arrives as float64(1) while still needing to satisfy a type: integer check.

The create tool's attrs parameter is widened to map[string]any. A string-typed Go map generated additionalProperties: {type: string}, which cannot express a number by construction. The CLI --attrs flag keeps its string map, since its values genuinely arrive as text.

Nested map keys are now sorted, so repeated writes of the same value produce byte-identical YAML.

Unqualified flight create used the wrong namespace (#74)

tap flight create NAME resolved its namespace from defaultNamespace, the user's personal namespace. Working in an org KEG and typing a bare flight name created the flight under the personal namespace instead. Nothing failed loudly: the flight was valid, simply absent from the org's flights page, and able to collide with a later explicitly qualified one.

The active KEG's namespace now comes first in the precedence chain. The selector chain matches resolveIdentity and resolveKegAdminRef, so flight creation agrees with what tap info reports as active. Only a namespace the KEG selector states explicitly counts, so a bare keg name still falls through rather than being resolved into the personal namespace and presented as if the KEG had named it.

Auth failures gave no actionable guidance (#87)

An auth failure fell through to bare error text with no structured content, so an agent whose session lost access had no indication that reorienting is the refresh boundary.

Wording alone would have been false advice. The credential store is loaded once per process, so a token written by tap auth login in a separate shell was invisible to a running MCP server and no amount of reorienting could clear the 401. Orientation already reloads configuration; credentials now reload on the same boundary, including the resolved keg cache whose entries captured the previous resolver.

Auth errors return structured content with a code, an action, and reorientRequired. A 403 is kept distinct: the credential was accepted and the caller simply lacks the grant, so logging in again changes nothing and reorientRequired stays false rather than starting a loop that cannot succeed. Neither message carries token or credential detail.

Namespace list covered only one hub (#73)

tap namespace list queried only the selected hub, hiding memberships on every other configured hub, and its output carried no hub column so rows were unattributable.

With no --hub, every configured remote and readonly hub is queried and each row names its source hub, so the same namespace name on two hubs stays two distinct rows. An unreachable hub becomes a warning naming that hub while the remaining hubs still list, mirroring how HubListKegs already handles aggregate mode. An explicit --hub keeps its previous behavior and still propagates that hub's error.

Partial. The issue also asks for local namespaces. Tapper has no local hub kind: config admits only remote and readonly, and every resolver rejects anything else. Representing locally-backed namespaces needs that kind to exist first, so this is left open.

Follow-up worth filing separately

Flight references resolve three different ways. Read operations search every configured hub for a unique slug match, write operations use the single-namespace default fixed here, and MCP graph selection resolves against the pinned flight root. Unifying them is a larger change than this PR should carry.

Verification

go build ./... and go test ./... pass. New coverage: numeric and nested-map metadata round-tripping, the flight namespace precedence table, the auth error payload including the no-credential-leak criterion, credential reload after a login written by another process, and namespace aggregation across two hubs including the unreachable-hub case.

Manual end-to-end checks against a running hub are still outstanding.

valueToYAMLNode tagged every integer and float !!str, so any schema field
typed `integer` was unwritable through the MCP create and edit tools. A
schema marking such a field required became impossible to satisfy: the
node could not be created, and meta could not repair it afterwards
because meta validates the whole node including markdown sections.

Tag integers !!int and floats !!float, matching the !!bool case that was
already correct. Integral floats serialize as plain integers because JSON
has no integer type, so a value sent over MCP as 1 arrives as float64(1)
and must still satisfy a `type: integer` check.

Widen the MCP create tool's attrs parameter to map[string]any. A
string-typed Go map generates additionalProperties: {type: string}, which
cannot express a number by construction. The CLI --attrs flag keeps its
string map, since its values genuinely arrive as text.

Also sort nested map keys so repeated writes of the same value produce
byte-identical YAML.

Fixes #91
An unqualified `tap flight create NAME` resolved its namespace from
defaultNamespace, which is the user's personal namespace. Working in an
org KEG and typing a bare flight name therefore created the flight under
the personal namespace instead. The result was valid, so nothing failed
loudly: the flight was simply absent from the org's flights page and
could later collide with an explicitly qualified one.

Put the active KEG's namespace first in the precedence chain, ahead of
defaultNamespace, fallbackNamespace, and the hub's own default. The
selector chain matches resolveIdentity and resolveKegAdminRef, so
`tap flight create` now agrees with what `tap info` reports as active.

Only a namespace the KEG selector states explicitly counts. A bare keg
name still falls through to defaultNamespace rather than being resolved
into the personal namespace and presented as if the KEG had named it.

Explicitly qualified @namespace/+slug references never reach the
defaulting path and are unchanged.

Fixes #74
An auth failure fell through to bare error text with no structured
content, so an agent whose session lost access had no indication that
reorienting is the refresh boundary and tended to treat the session as
dead.

Telling an agent to log in and reorient would have been false advice on
its own. The credential store is loaded once per process, so a token
written by `tap auth login` in a separate shell was invisible to a
running MCP server and no amount of reorienting could clear the 401.
Orientation already reloads configuration; credentials now reload on the
same boundary, including the resolved keg cache, whose entries captured
the previous resolver.

Auth errors now return structured content with a code, an action, and
reorientRequired. A 403 is kept distinct: the credential was accepted and
the caller simply lacks the grant, so logging in again changes nothing
and reorientRequired stays false rather than starting a loop that cannot
succeed. Neither message carries token or credential detail.

Fixes #87
`tap namespace list` queried only the selected hub, so memberships on
every other configured hub were silently absent, and the output carried
no hub column, leaving rows unattributable when more than one hub is
configured.

With no --hub, every configured remote and readonly hub is now queried
and each row names its source hub, so the same namespace name on two hubs
stays two distinct rows. A hub that cannot be reached becomes a warning
naming that hub and the remaining hubs still list, mirroring how
HubListKegs already handles aggregate mode. An explicit --hub keeps its
previous single-hub behaviour and still propagates that hub's error.

Local namespaces are not included. Tapper has no local hub kind: config
admits only remote and readonly, and every resolver rejects anything
else. Representing locally-backed namespaces needs that kind to exist
first.

Refs #73
Exercises the create and edit paths through a real keg with the schema
from the issue, rather than asserting at the metadata layer alone. Both
cases fail before the fix with the exact error the issue reports.

Refs #91
@jlrickert
jlrickert marked this pull request as ready for review September 2, 2026 04:54
@jlrickert
jlrickert merged commit 7c51cad into main Sep 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant