gohugoio/hugo · error

npm pack: malformed package.json

Error message

npm pack: malformed package.json

What it means

When the root package.json has no `workspaces` key at all, Hugo appends one by inserting text before the file's final closing brace. If the raw bytes contain no `}` at all, the file can't be a JSON object and Hugo bails out. Note the earlier json.Unmarshal succeeded, so this state means the parsed value wasn't a normal object body — an edge case guard against writing garbage.

Source

Thrown at modules/npm/package_builder.go:332

						insertion = ",\n" + indent + indent + quoted + "\n" + indent
					}
					result := make([]byte, 0, len(data)+len(insertion))
					result = append(result, data[:pos]...)
					result = append(result, insertion...)
					result = append(result, data[pos:]...)
					return afero.WriteFile(fsys, packageJSONName, result, 0o666)
				}
			}
		}

		// We know a workspaces array exists but could not locate it reliably in the raw data.
		return fmt.Errorf("npm pack: could not locate existing workspaces array for insertion")
	}

	// No workspaces key — add before closing brace.
	lastBrace := bytes.LastIndexByte(data, '}')
	if lastBrace < 0 {
		return fmt.Errorf("npm pack: malformed package.json")
	}

	before := bytes.TrimRight(data[:lastBrace], " \t\n\r")
	needComma := len(before) > 0 && before[len(before)-1] != '{' && before[len(before)-1] != ','

	var insertion string
	if needComma {
		insertion = ",\n" + indent + `"workspaces": [` + "\n" + indent + indent + quoted + "\n" + indent + "]\n"
	} else {
		insertion = indent + `"workspaces": [` + "\n" + indent + indent + quoted + "\n" + indent + "]\n"
	}

	result := make([]byte, 0, len(data)+len(insertion))
	result = append(result, before...)
	result = append(result, insertion...)
	result = append(result, data[lastBrace:]...)
	return afero.WriteFile(fsys, packageJSONName, result, 0o666)
}

View on GitHub ↗ (pinned to 8a468df065)

Solutions

  1. Inspect package.json and restore it to a valid JSON object (`{ ... }`) — regenerate with `npm init -y` if needed.
  2. Check for truncation from interrupted writes or disk-full conditions, restore from git (`git checkout -- package.json`).
  3. Re-run `hugo mod npm pack` after the file is a well-formed object.
Defensive patterns

Strategy: validation

Validate before calling

var v map[string]any
if err := json.Unmarshal(mustRead("package.json"), &v); err != nil {
    return fmt.Errorf("package.json malformed: %w", err)
}
// also ensure top level is an object, not an array/scalar

Type guard

func isJSONObjectFile(data []byte) bool {
    var v map[string]any
    return json.Unmarshal(data, &v) == nil
}

Try / catch

if err := b.Build(); err != nil {
    if strings.Contains(err.Error(), "malformed package.json") {
        log.Fatal("package.json must be a JSON object; run `npm pkg fix` or recreate with `npm init -y`")
    }
    return err
}

Prevention

When it happens

Trigger: `hugo mod npm pack` on a package.json whose bytes contain no closing brace — practically only possible if the file parses as something odd (this path requires unmarshal into map to have succeeded, so it is a defensive guard; near-zero-byte or corrupted files hit the parse error first).

Common situations: Truncated or corrupted package.json produced by an interrupted write; extremely unusual encodings. Most real-world corruption is caught earlier by the parse-error path (index 122); this guard covers the residual gap.

Related errors


AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31). Data as JSON: /data/errors/13bd7ec608a1266b.json. Report an issue: GitHub ↗.