gohugoio/hugo · error

npm pack: malformed package.json

Error message

npm pack: malformed package.json

What it means

In ensureWorkspaceRef (modules/npm/package_builder.go:332). When there is no existing workspaces key, Hugo inserts one before the last '}' of package.json. bytes.LastIndexByte(data, '}') returns -1 when no closing brace is present, so the file is declared malformed. Although the earlier json.Unmarshal would normally catch bad JSON, this guards the byte-edit step.

Solutions

  1. Restore package.json from version control or regenerate it.
  2. Validate it parses as JSON and ends with '}'.
  3. Prevent concurrent writers from touching package.json during 'hugo'.

Example fix

// before: truncated package.json
{ "name": "proj", "dependencies": { "x": "^1.0"

// after
{
  "name": "proj",
  "dependencies": { "x": "^1.0" }
}
Defensive patterns

Strategy: validation

Validate before calling

data, err := os.ReadFile("package.json")
if err != nil {
    return err
}
if bytes.LastIndexByte(data, '}') < 0 {
    return errors.New("package.json is malformed: missing closing brace")
}
var v any
return json.Unmarshal(data, &v)

Prevention

When it happens

Trigger: package.json has no closing '}' — truncated, or assembled from fragments. This typically implies the file became corrupt after the earlier unmarshal or the read/parse/write path is inconsistent.

Common situations: A truncated package.json written by a crashed process; a file stripped of its closing brace by a bad sed/regex edit; concurrent writes during the build.

Understand the failure class

Related errors


AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09). Data as JSON: /api/errors/13bd7ec608a1266b. Report an issue: GitHub.

Appendix: 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 52c9bd7908)