golang/go · error

build output %q already exists and is not an object file

Error message

build output %q already exists and is not an object file

What it means

Thrown by checkDstOverwrite when the build output path exists, is a non-empty regular file, is not recognized as an object file (isObject returns false), and the build is not forced. This protects user files from being clobbered by an accidental `go build -o`.

Source

Thrown at src/cmd/go/internal/work/shell.go:256

// When running as a user with sufficient privileges, we may delete
// even device files, for example, which is not intended.
func mayberemovefile(s string) {
	if fi, err := os.Lstat(s); err == nil && !fi.Mode().IsRegular() {
		return
	}
	os.Remove(s)
}

// Be careful about removing/overwriting dst.
// Do not remove/overwrite if dst exists and is a directory
// or a non-empty non-object file.
func checkDstOverwrite(dst string, force bool) error {
	if fi, err := os.Stat(dst); err == nil {
		if fi.IsDir() {
			return fmt.Errorf("build output %q already exists and is a directory", dst)
		}
		if !force && fi.Mode().IsRegular() && fi.Size() != 0 && !isObject(dst) {
			return fmt.Errorf("build output %q already exists and is not an object file", dst)
		}
	}
	return nil
}

// writeFile writes the text to file.
func (sh *Shell) writeFile(file string, text []byte) error {
	if cfg.BuildN || cfg.BuildX {
		switch {
		case len(text) == 0:
			sh.ShowCmd("", "echo -n > %s # internal", file)
		case bytes.IndexByte(text, '\n') == len(text)-1:
			// One line. Use a simpler "echo" command.
			sh.ShowCmd("", "echo '%s' > %s # internal", bytes.TrimSuffix(text, []byte("\n")), file)
		default:
			// Use the most general form.
			sh.ShowCmd("", "cat >%s << 'EOF' # internal\n%sEOF", file, text)
		}

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Choose a fresh output filename that does not collide with existing data.
  2. Rename or back up the existing file before building.
  3. Confirm the existing file is safe to overwrite, then force: `go build -o notes.txt` is not forceable in user space — instead delete it first (`rm notes.txt`) or use a different name.
  4. Verify isObject expectations: the check only auto-overwrites recognized object/binary files.

Example fix

# before
// go build -o run.sh   (run.sh exists, non-empty, not an object)

# after
// mv run.sh run.sh.bak && go build -o run.sh
Defensive patterns

Strategy: validation

Validate before calling

// Refuse to clobber non-object files
out := flag.Arg(0)
if fi, err := os.Stat(out); err == nil && fi.Mode().IsRegular() && fi.Size() != 0 && !isObject(out) {
    log.Fatalf("%s exists and is not an object file; rename or remove first", out)
}

Type guard

func isObject(p string) bool {
    f, err := os.Open(p)
    if err != nil { return false }
    defer f.Close()
    var magic [4]byte
    n, _ := f.Read(magic[:])
    if n < 4 { return false }
    // ELF \x7fELF, Mach-O \xfe\xed\xfa\xce/\xcf, PE "MZ"
    return bytes.Equal(magic[:4], []byte{0x7f, 'E', 'L', 'F'}) ||
        magic[0] == 'M' && magic[1] == 'Z'
}

Prevention

When it happens

Trigger: Run `go build -o notes.txt` (or any existing non-object file) without force; os.Stat finds a regular file with Size()!=0, isObject(dst) is false, force is false, so the error fires.

Common situations: Reusing a filename that holds a README/script/data file; CI sharing an output name with an artifact from another tool; pointing `-o` at an existing shell script.

Related errors


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/56d1c6133bb3f3fa. Report an issue: GitHub.