fix(deploy): validate deployment id as 8-hex app_id, not UUID#43
Merged
Conversation
The six deployment-by-id tools (get_deployment, get_deployment_events,
redeploy, delete_deployment, update_deploy_env, wake_deployment) applied
uuidSchema (canonical 8-4-4-4-12) to their `id` arg. But a deployment's
public id (app_id/token, returned as `deploy_id` by create_deploy) is
8-char lowercase hex — the api's generateAppID() is hex.EncodeToString of
4 random bytes (api/internal/handlers/deploy.go). zod therefore rejected
EVERY real call client-side (-32602) before it reached the api, killing
the entire manage-the-deploy loop through MCP.
Introduce deployIdSchema (/^[0-9a-f]{8}$/) and apply it to exactly those
six tools; resource-token tools keep uuidSchema (their tokens ARE UUIDs).
Why it shipped green: (a) test/mock-api.ts was edited to EMIT UUID-shaped
app_ids "like prod" — but prod does not, so fixtures never tripped the bad
schema; (b) the handler tests call .handler directly, bypassing the zod
inputSchema the real SDK dispatch applies. Revert the mock to emit 8-hex
app_ids and add a regression test that drives the registered inputSchema
(the dispatch path) for all six tools, iterating the registry (rule 18).
Also pin zod in dependencies (was an implicit SDK peer).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Symptom
A live dogfood found the deployment wedge is broken end-to-end through MCP. Six deployment-by-id tools —
get_deployment,get_deployment_events,redeploy,delete_deployment,update_deploy_env,wake_deployment— applieduuidSchema(canonical 8-4-4-4-12 UUID) to theirid/app_id argument. But a deployment app_id is 8-char lowercase hex, not a UUID. So zod rejected every real call client-side with-32602before it reached the api. An agent couldcreate_deploy(receivesdeploy_idlikea3f91c0e) and then could NOT poll status, read failure events, redeploy, delete, update env, or wake it. The whole manage-the-deploy loop was dead through MCP.Root cause / why it shipped green
Two compounding masks let 161 tests pass while every real call was broken:
test/mock-api.tsto EMIT UUID-shaped app_ids ("now the mock returns a UUID-shaped app_id like prod") — but prod does not emit UUID app_ids. The mock was made to match the wrong schema, so fixtures never tripped it.server._registeredTools[name].handler(...)directly, bypassing the zodinputSchemathe real MCP SDK dispatch applies at the wire boundary. The validation that breaks in prod was never exercised in test.Ground truth (verified)
generateAppID()(api/internal/handlers/deploy.go):hex.EncodeToStringof 4 random bytes ⇒ exactly 8 lowercase hex chars. Comment: "generateAppID produces an 8-char lowercase hex string via crypto/rand."DeployItem:app_id= "8-char public identifier used in the URL";token= "Public-facing alias for app_id (same 8-char value)". (idis the DB row PK,format: uuid— but the URL identifier isapp_id/token.)/deploy/{id},/deploy/{id}/events,/deploy/{id}/wake,/api/v1/deployments/{id}are all bare{type: string}.uuidSchemastays correct fordelete_resource,presign_storage,pause_resource,resume_resource,rotate_credentials. Those are untouched.Fix
deployIdSchema = z.string().regex(/^[0-9a-f]{8}$/, ...)(named constAPP_ID_REGEX), matching the api's real format with a precise agent-facing error message.deployIdSchemato exactly the 6 deployment-id tools.uuidSchemaleft on the 5 resource-token tools.test/mock-api.tsnow emits 8-hex app_ids (via agenerateAppId()helper mirroring the api'sgenerateAppID()), socreate_deploy→ the manage tools round-trips in tests the way it does in prod. The DB rowidstays a UUID (matches openapi).test/deploy-id-schema-unit.test.ts) that drives the registeredinputSchema(the SDK dispatch path, NOThandlerFor), iterating the six tools as a registry: asserts each ACCEPTSa3f91c0eand REJECTS a UUID + an obviously-bad value, plusdeployIdSchema.parseunit contracts. Also corrected the staleinput-hardening-unit.test.tsblock that codified the lie, and the twointegration.test.ts404 tests (which DO go through realclient.callTooldispatch — they passed a UUID that would now be rejected before reaching the api).zodindependencies(4.3.6, the lockfile-resolved version) —src/index.tsimports zod but it was only resolving as an SDK peer.Coverage block (rule 17)
Notes
Follow-ups (NOT in this PR — MCP-vs-API tool gaps the dogfood flagged)
get_deploy_logs— api exposesGET /deploy/{id}/logs; no MCP tool wraps it.redeploy— single-appredeployexists; no equivalent for multi-service stacks.🤖 Generated with Claude Code