flipped-aurora/gin-vue-admin · error

原子发布 %s 失败: %w

Error message

原子发布 %s 失败: %w

What it means

Raised when the final atomic publish step os.Rename(stagedPath, targetPath) fails. Staged content is written to a temp file in the project root and moved into place with rename for atomicity; rename failures abort the publish of that file and trigger rollback of already-published files.

Source

Thrown at server/service/system/auto_code_task.go:209

	currentHash, exists, err := hashAutoCodeTarget(file.TargetPath)
	if err != nil {
		return false, err
	}
	if exists && currentHash == file.AfterHash {
		if removeErr := os.Remove(file.StagedPath); removeErr != nil && !errors.Is(removeErr, fs.ErrNotExist) {
			return false, fmt.Errorf("清理重复 staging 文件失败: %w", removeErr)
		}
		file.StagedPath = ""
		return false, nil
	}
	if exists != file.Existed || (file.Existed && currentHash != file.BeforeHash) {
		return false, fmt.Errorf("%w: %s", errAutoCodeFileConflict, file.TargetPath)
	}
	if err = os.MkdirAll(filepath.Dir(file.TargetPath), 0o755); err != nil {
		return false, fmt.Errorf("创建目标目录 %s 失败: %w", filepath.Dir(file.TargetPath), err)
	}
	if err = os.Rename(file.StagedPath, file.TargetPath); err != nil {
		return false, fmt.Errorf("原子发布 %s 失败: %w", file.TargetPath, err)
	}
	file.StagedPath = ""
	return true, nil
}

func (task *autoCodeFileTask) rollback(applied []int) error {
	for i := len(applied) - 1; i >= 0; i-- {
		index := applied[i]
		if index < 0 || index >= len(task.files) {
			return fmt.Errorf("自动代码回滚索引无效: %d", index)
		}
		file := &task.files[index]
		currentHash, exists, err := hashAutoCodeTarget(file.TargetPath)
		if err != nil {
			return err
		}
		if file.Existed && exists && currentHash == file.BeforeHash {
			continue

View on GitHub (pinned to 3136500ef3)

Solutions

  1. Ensure the repo root, server/ and web/ trees live on the same local filesystem (avoid mounting web/ on a different device than the root that hosts the staging dir)
  2. Fix permissions on the target directory/existing file so the server process can replace it
  3. Verify the target path is a regular file, not a directory
  4. Re-run after closing processes that lock target files; check mount layout if EXDEV is reported

Example fix

// before: web on separate mount -> EXDEV on rename
/dev/sdb1 on /project/web type ext4

// after: keep web/ inside the repo root filesystem (same device as staging dir)
$ mv /mnt/otherdisk/web ./web  # then regenerate/re-run
Defensive patterns

Strategy: validation

Validate before calling

func sameDevice(a, b string) error {
	aInfo, err := os.Stat(a)
	if err != nil {
		return err
	}
	bInfo, err := os.Stat(b)
	if err != nil {
		return err
	}
	aSys, bSys := aInfo.Sys().(*syscall.Stat_t), bInfo.Sys().(*syscall.Stat_t)
	if aSys.Dev != bSys.Dev {
		return fmt.Errorf("%s and %s on different devices (rename would fail with EXDEV)", a, b)
	}
	return nil
}
// check repo root (staging parent) vs each target's directory before committing

Try / catch

if err := commitAutoCodeFileTask(task, publishPreparedAutoCodeFile, persist); err != nil {
	if strings.Contains(err.Error(), "原子发布") {
		var pathErr *os.LinkError
		if errors.As(err, &pathErr) && errors.Is(pathErr, syscall.EXDEV) {
			// reconfigure layout so staging dir and targets share one filesystem
		}
	}
}

Prevention

When it happens

Trigger: os.Rename fails with EXDEV (staging dir and target on different filesystems/mounts), permission denied on the target directory or existing target file, target became a directory, or the staged file was removed mid-task.

Common situations: web/ or server/ is a separate mount/symlink into another filesystem while the staging dir lives in the repo root (EXDEV); target file owned by root or another user; editors/watchers holding the target open with delete restrictions on some platforms; Docker volume boundary between staging and target.

Related errors


AI-assisted analysis of flipped-aurora/gin-vue-admin@3136500ef3 (2026-08-31). Data as JSON: /api/errors/0df4856d182366cc. Report an issue: GitHub.