diff --git a/test/test-mv-hardlink.sh b/test/test-mv-hardlink.sh
new file mode 100755
index 0000000..2963ef3
--- /dev/null
+++ b/test/test-mv-hardlink.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+#
+# SPDX-License-Identifier: LGPL-2.1-only
+#
+# Test that rename (mv) from outside PSEUDO_INCLUDE_PATHS followed by
+# hardlink properly tracks file ownership.
+#
+# Reproduces: https://lists.openembedded.org/g/openembedded-core/message/236712
+#
+# In Yocto, files are often moved from ${B} (build dir, outside
+# PSEUDO_INCLUDE_PATHS) to ${D} (image dir, tracked by pseudo) and
+# then hardlinked. If pseudo doesn't track the rename, the hardlink
+# gets recorded with the real UID, causing inconsistent ownership
+# and pseudo abort on subsequent stat.
+
+# Create two directories:
+#   srcdir  - simulates ${B}, outside PSEUDO_INCLUDE_PATHS
+#   destdir - simulates ${D}, inside PSEUDO_INCLUDE_PATHS
+# Use realpath to resolve symlinks, since pseudo canonicalizes paths
+# internally and the PSEUDO_INCLUDE_PATHS prefix must match.
+srcdir=$(mktemp -d "$(realpath "${PWD}")/mv_hl_src_XXXXXX")
+destdir=$(mktemp -d "$(realpath "${PWD}")/mv_hl_dest_XXXXXX")
+trap "rm -rf '$srcdir' '$destdir'" EXIT
+
+# Restrict pseudo tracking to only destdir
+export PSEUDO_INCLUDE_PATHS="$destdir"
+
+echo hello > ${srcdir}/hello.txt
+
+mv ${srcdir}/hello.txt ${destdir}/hello.txt
+ln ${destdir}/hello.txt ${destdir}/hello2.txt
+
+# Both files should report uid 0 under pseudo
+dest_uid=$(\ls -n1 ${destdir}/hello.txt | awk '{ print $3 }')
+link_uid=$(\ls -n1 ${destdir}/hello2.txt | awk '{ print $3 }')
+
+if [ "$dest_uid" != "0" ]; then
+    echo "FAIL: dest uid is $dest_uid, expected 0"
+    exit 1
+fi
+
+if [ "$link_uid" != "0" ]; then
+    echo "FAIL: link uid is $link_uid, expected 0"
+    exit 1
+fi
+
+if [ "$dest_uid" != "$link_uid" ]; then
+    echo "FAIL: UIDs don't match (dest=$dest_uid, link=$link_uid)"
+    exit 1
+fi
+
+exit 0
diff --git a/test/test-rename-hardlink.c b/test/test-rename-hardlink.c
new file mode 100644
index 0000000..d3f7384
--- /dev/null
+++ b/test/test-rename-hardlink.c
@@ -0,0 +1,87 @@
+/*
+ * Test that rename() from outside PSEUDO_INCLUDE_PATHS followed by
+ * hardlink properly tracks file ownership.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+#define _GNU_SOURCE
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+
+static int failures = 0;
+
+static void check(const char *desc, int condition) {
+	if (!condition) {
+		fprintf(stderr, "FAIL: %s\n", desc);
+		failures++;
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	struct stat st1, st2;
+	char src_path[PATH_MAX];
+	char dest_path[PATH_MAX];
+	char link_path[PATH_MAX];
+	int fd;
+
+	if (argc != 3) {
+		fprintf(stderr, "Usage: %s <src_dir> <dest_dir>\n", argv[0]);
+		return 1;
+	}
+
+	/* Create a file in src_dir (outside PSEUDO_INCLUDE_PATHS) */
+	snprintf(src_path, sizeof(src_path), "%s/testfile.txt", argv[1]);
+	fd = open(src_path, O_CREAT | O_WRONLY, 0644);
+	if (fd < 0) {
+		perror("create source file");
+		return 1;
+	}
+	if (write(fd, "hello\n", 6) != 6) {
+		perror("write");
+		close(fd);
+		return 1;
+	}
+	close(fd);
+
+	/* rename() from untracked src_dir to tracked dest_dir */
+	snprintf(dest_path, sizeof(dest_path), "%s/testfile.txt", argv[2]);
+	if (rename(src_path, dest_path) != 0) {
+		perror("rename");
+		return 1;
+	}
+
+	/* Create a hardlink in the tracked directory */
+	snprintf(link_path, sizeof(link_path), "%s/testfile2.txt", argv[2]);
+	if (link(dest_path, link_path) != 0) {
+		perror("link");
+		return 1;
+	}
+
+	/* Stat both files and verify consistent uid 0 */
+	if (stat(dest_path, &st1) != 0) {
+		perror("stat dest");
+		return 1;
+	}
+	if (stat(link_path, &st2) != 0) {
+		perror("stat link");
+		return 1;
+	}
+
+	check("same inode", st1.st_ino == st2.st_ino);
+	check("UIDs match", st1.st_uid == st2.st_uid);
+	check("dest uid is 0", st1.st_uid == 0);
+	check("link uid is 0", st2.st_uid == 0);
+
+	unlink(link_path);
+	unlink(dest_path);
+
+	return failures;
+}
diff --git a/test/test-rename-hardlink.sh b/test/test-rename-hardlink.sh
new file mode 100755
index 0000000..8f2be60
--- /dev/null
+++ b/test/test-rename-hardlink.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+#
+# SPDX-License-Identifier: LGPL-2.1-only
+#
+# Test that rename() from outside PSEUDO_INCLUDE_PATHS followed by
+# hardlink properly tracks file ownership.
+
+srcdir=$(mktemp -d "$(realpath "${PWD}")/ren_hl_src_XXXXXX")
+destdir=$(mktemp -d "$(realpath "${PWD}")/ren_hl_dest_XXXXXX")
+trap "rm -rf '$srcdir' '$destdir'" EXIT
+
+export PSEUDO_INCLUDE_PATHS="$destdir"
+
+./test/test-rename-hardlink "$srcdir" "$destdir"
diff --git a/test/test-renameat-hardlink.c b/test/test-renameat-hardlink.c
new file mode 100644
index 0000000..9c4840c
--- /dev/null
+++ b/test/test-renameat-hardlink.c
@@ -0,0 +1,101 @@
+/*
+ * Test that renameat() from outside PSEUDO_INCLUDE_PATHS followed by
+ * hardlink properly tracks file ownership.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+#define _GNU_SOURCE
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+
+static int failures = 0;
+
+static void check(const char *desc, int condition) {
+	if (!condition) {
+		fprintf(stderr, "FAIL: %s\n", desc);
+		failures++;
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	struct stat st1, st2;
+	char dest_path[PATH_MAX];
+	char link_path[PATH_MAX];
+	int fd, olddirfd, newdirfd;
+
+	if (argc != 3) {
+		fprintf(stderr, "Usage: %s <src_dir> <dest_dir>\n", argv[0]);
+		return 1;
+	}
+
+	/* Open directory fds for renameat */
+	olddirfd = open(argv[1], O_RDONLY | O_DIRECTORY);
+	if (olddirfd < 0) {
+		perror("open src_dir");
+		return 1;
+	}
+	newdirfd = open(argv[2], O_RDONLY | O_DIRECTORY);
+	if (newdirfd < 0) {
+		perror("open dest_dir");
+		close(olddirfd);
+		return 1;
+	}
+
+	/* Create a file in src_dir (outside PSEUDO_INCLUDE_PATHS) */
+	fd = openat(olddirfd, "testfile.txt", O_CREAT | O_WRONLY, 0644);
+	if (fd < 0) {
+		perror("create source file");
+		return 1;
+	}
+	if (write(fd, "hello\n", 6) != 6) {
+		perror("write");
+		close(fd);
+		return 1;
+	}
+	close(fd);
+
+	/* renameat() from untracked src_dir to tracked dest_dir */
+	if (renameat(olddirfd, "testfile.txt", newdirfd, "testfile.txt") != 0) {
+		perror("renameat");
+		return 1;
+	}
+
+	/* Create a hardlink using linkat in the tracked directory */
+	if (linkat(newdirfd, "testfile.txt", newdirfd, "testfile2.txt", 0) != 0) {
+		perror("linkat");
+		return 1;
+	}
+
+	/* Stat both files and verify consistent uid 0 */
+	snprintf(dest_path, sizeof(dest_path), "%s/testfile.txt", argv[2]);
+	snprintf(link_path, sizeof(link_path), "%s/testfile2.txt", argv[2]);
+
+	if (stat(dest_path, &st1) != 0) {
+		perror("stat dest");
+		return 1;
+	}
+	if (stat(link_path, &st2) != 0) {
+		perror("stat link");
+		return 1;
+	}
+
+	check("same inode", st1.st_ino == st2.st_ino);
+	check("UIDs match", st1.st_uid == st2.st_uid);
+	check("dest uid is 0", st1.st_uid == 0);
+	check("link uid is 0", st2.st_uid == 0);
+
+	unlinkat(newdirfd, "testfile2.txt", 0);
+	unlinkat(newdirfd, "testfile.txt", 0);
+	close(olddirfd);
+	close(newdirfd);
+
+	return failures;
+}
diff --git a/test/test-renameat-hardlink.sh b/test/test-renameat-hardlink.sh
new file mode 100755
index 0000000..b7e3a9e
--- /dev/null
+++ b/test/test-renameat-hardlink.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+#
+# SPDX-License-Identifier: LGPL-2.1-only
+#
+# Test that renameat() from outside PSEUDO_INCLUDE_PATHS followed by
+# hardlink properly tracks file ownership.
+
+srcdir=$(mktemp -d "$(realpath "${PWD}")/renat_hl_src_XXXXXX")
+destdir=$(mktemp -d "$(realpath "${PWD}")/renat_hl_dest_XXXXXX")
+trap "rm -rf '$srcdir' '$destdir'" EXIT
+
+export PSEUDO_INCLUDE_PATHS="$destdir"
+
+./test/test-renameat-hardlink "$srcdir" "$destdir"
