{"record":{"id":"b6385abb87003a24","repo":"Hmbown/CodeWhale","slug":"publish-package-list-is-missing-workspace-crates-missing","errorCode":null,"errorMessage":"publish package list is missing workspace crates: {missing} / publish package list contains non-workspace crates: {extra}","messagePattern":"publish package list is missing workspace crates: (.+?) / publish package list contains non-workspace crates: (.+?)","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"error","filePath":"scripts/release/validate-crate-publish-order.py","lineNumber":118,"sourceCode":"        rendered = \", \".join(str(version) for version in versions)\n        raise ValidationError(f\"workspace packages have mixed versions: {rendered}\")\n\n    workspace_by_name = {package[\"name\"]: package for package in packages}\n    release_names = sorted(\n        name for name in workspace_by_name if name.startswith(\"codewhale-\")\n    )\n    ordered_set = set(ordered_crates)\n    missing = sorted(set(release_names) - ordered_set)\n    extra = sorted(ordered_set - set(release_names))\n    if missing or extra:\n        messages = []\n        if missing:\n            messages.append(\"publish package list is missing workspace crates: \" + \" \".join(missing))\n        if extra:\n            messages.append(\n                \"publish package list contains non-workspace crates: \" + \" \".join(extra)\n            )\n        raise ValidationError(\"\\n\".join(messages))\n\n    positions = {name: index for index, name in enumerate(ordered_crates)}\n    has_workspace_dependencies = {name: False for name in release_names}\n    publish_edges: set[tuple[str, str, str]] = set()\n    for dependent in release_names:\n        dependencies = workspace_by_name[dependent].get(\"dependencies\", [])\n        if not isinstance(dependencies, list):\n            raise ValidationError(f\"Cargo metadata dependencies for {dependent} must be a list\")\n        for dependency in dependencies:\n            if not isinstance(dependency, dict) or dependency.get(\"path\") is None:\n                continue\n            dependency_name = dependency.get(\"name\")\n            if dependency_name not in workspace_by_name:\n                continue\n            has_workspace_dependencies[dependent] = True\n            kind = dependency.get(\"kind\") or \"normal\"\n            # Cargo does not compile dev-dependencies while verifying a publish.\n            # They may legitimately point back across the publication DAG.","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/scripts/release/validate-crate-publish-order.py#L100-L136","documentation":"The validator compares the crates listed in scripts/release/crates.sh (ordered_crates, the publish order) against the codewhale-* crates found in cargo metadata. If the two sets differ in either direction — a workspace release crate missing from the publish list, or a listed crate that is not in the workspace — it raises a combined error listing the missing and extra names.","triggerScenarios":"Adding a new codewhale-* crate to the workspace without appending it to crates.sh; renaming or removing a crate without updating crates.sh; listing a crate in crates.sh that no longer exists or is not named codewhale-*.","commonSituations":"Release-day checklist misses the new crate; a crate is renamed (e.g. codewhale-core → codewhale-protocol) and crates.sh keeps the old name; crates.sh contains a typo.","solutions":["Read the `missing`/`extra` names in the error message.","Add each missing workspace crate to scripts/release/crates.sh in correct dependency order.","Remove or correct each extra entry that is not a workspace crate.","Re-run the validator; if order errors appear next, fix positions before re-checking."],"exampleFix":"// before (scripts/release/crates.sh)\nCRATES=(codewhale-config codewhale-protocol)\n// after\nCRATES=(codewhale-config codewhale-protocol codewhale-tui)","handlingStrategy":"validation","validationCode":"import subprocess, json\nmeta = json.loads(subprocess.check_output(['cargo','metadata','--format-version','1']))\nws = {p['name'] for p in meta['packages'] if p['name'].startswith('codewhale-')}\nlisted = set(read_crates_sh())\nprint('missing:', ws - listed, 'extra:', listed - ws)","typeGuard":null,"tryCatchPattern":"try:\n    validate_order(...)\nexcept ValidationError as e:\n    for line in str(e).splitlines(): print('fix inventory:', line)","preventionTips":["Add the crates.sh update to the checklist for every new codewhale-* crate","Generate crates.sh from cargo metadata instead of maintaining it by hand","Rename crates and crates.sh entries in the same commit"],"tags":["release","cargo","publish","drift"],"backgroundTag":"schema-validation-failed","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}