Skip to content

A directory at .socket/manifest.json exits 2 with no --json output when --manifest-path spells the path, but exits 1 with manifest_unreadable under the default path #1123

Description

[agent] Filed by the scheduled architecture audit routine (CLI and core). Register: discussion #560 register comment.

Kind: bug. Source: new finding, register C76. It's a regression from #1029 and a sixth copy of the manifest-shape rule in C56 (#998).

Problem

#1029 added GlobalArgs::validate_paths, which runs once in main.rs#L81-L84 before dispatch. It refuses a manifest that is a directory, but only when the string passed to --manifest-path differs from the default:

if self.manifest_path != DEFAULT_PATCH_MANIFEST_PATH {
    let manifest = self.resolved_manifest_path();
    if manifest.is_dir() { return Err(... "is a directory, not a manifest file") }

So the same file on disk gets two verdicts, depending on how the path is spelled:

  • Explicit spelling (./.socket/manifest.json, an absolute path, or SOCKET_MANIFEST_PATH): exit 2 from main. It prints a bare Error: line on stderr and nothing on stdout, even under --json.
  • Default spelling: the command runs. read_manifest then refuses the non-regular file, and each command maps that error its own way.

The error message also doubles the prefix: `././.socket/manifest.json`, because cwd . is joined onto ./.socket/….

Proof by execution. A debug build at b96a785, run twice under env -i with --json --offline, with identical results. The fixture is mkdir -p .socket/manifest.json:

command default path --manifest-path ./.socket/manifest.json
list exit 1, manifest_unreadable exit 2, no JSON
apply exit 1, apply_failed exit 2, no JSON
apply --check exit 1, manifest_unreadable exit 2, no JSON
remove pkg:npm/a@1 exit 1, manifest_unreadable exit 2, no JSON
rollback exit 1, error string ./.socket/manifest.json is not a regular file exit 2, no JSON
vendor --check exit 1, manifest_unreadable exit 2, no JSON
vex -O /dev/null exit 2, manifest_unreadable exit 2, no JSON

A CI job therefore changes its exit code and loses its --json document depending on whether it spells out the default path.

Symptoms

None filed. Related: #998, where stat errors on the manifest are read as "no manifest"; #931, five codes for one unreadable manifest; #704, where exit-2 usage errors under --json use stderr only.

Impact: low to medium. This is not data loss, but it is a CI-gate verdict that depends on spelling, the same class of defect as the trust-signal fixes in #1029.

Proposed change

validate_paths should validate only what makes the flag a usage error, and do it by resolved path, not by string.

  • Option A (recommended): drop the is_dir check from validate_paths. A directory manifest is a corrupt-state error, not a usage error, and read_manifest already refuses it for every spelling. Keep the "project directory does not exist" check, which really is about the flag.
  • Option B: compare resolved_manifest_path() with cwd.join(DEFAULT_PATCH_MANIFEST_PATH) (lexically normalized) instead of comparing strings, and run the directory check for both. This makes the default path exit 2 as well, which is a contract change for manifest_unreadable.

Either way, the error message should print the normalized path (utils::relpath::normalize_lexically), not ././….

Size and scope

Acceptance criteria

  • list, apply, apply --check, remove, rollback, vendor --check and vex give the same exit code and --json output for a directory at .socket/manifest.json, whether the path is the default, ./.socket/manifest.json, an absolute path or SOCKET_MANIFEST_PATH.
  • A regression test parameterized over those four spellings, in tests/ through the hermetic builder.
  • The existing validate_paths tests stay green: a missing --cwd, a missing --global-prefix, and a manifest in a project directory that doesn't exist.
  • Error messages show a normalized path.

Dependencies

None. This lands independently of #998 and #931. If #704's usage_error helper lands first, option B's exit 2 should go through it.

Activity

  1. mikolalysenko commented on Oct 8, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Triaged as priority:p3 (general CLI). Confirmed on main: crates/socket-patch-cli/src/args.rs:434 runs the directory check only when the --manifest-path string differs from DEFAULT_PATCH_MANIFEST_PATH. Related to #998 and #931, but as the report says, the fix here is local to validate_paths, so it isn't clustered with them. No open PR addresses it yet.


    Generated by Claude Code

  2. added
    uxCLI commands, help, diagnostics, output consistency, or actionable recovery instructions.
    on Oct 9, 2026
  3. mikolalysenko commented on Oct 9, 2026

    @mikolalysenko
    CollaboratorAuthor

    v5 triage: P3, not a release blocker. A directory deliberately occupying manifest.json is malformed input. Retain P3 diagnostic consistency work, not a v5 gate.

    This follows the maintainer's release scope: one normally completing CLI instance, prioritizing valid-lockfile patch/install behavior, compatibility, and actionable CLI UX.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent:triagedarch-auditFiled by a scheduled architecture audit routine (see the architecture review discussion)bugSomething isn't workingpriority:p3uxCLI commands, help, diagnostics, output consistency, or actionable recovery instructions.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions