Shell installer: updating PATH replaces symlinked profiles and changes existing file permissions
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 85/100
Research direction
The bug is in the installer's PATH block rewriting logic, specifically the rewrite_path_block function that writes to a temporary file and moves it over the profile pathname. Start from the installer script (likely under scripts/install.sh) and the test fixture in scripts/install/test_install_sh.py. Use the provided reproduction script to confirm the issue, then modify the rewrite to resolve symlinks, preserve the original file's mode, and update the target file rather than replacing the symlink. Run the installer test suite to verify the fix.
Written by the indexing model from the issue text.
Description
What issue are you seeing?
When a shell profile already contains a Codex PATH block and is a symlink into a dotfiles directory, rerunning the shell installer replaces that symlink with a regular file. Its original dotfiles target is not updated. The same rewrite also changes an existing regular profile's mode to the temporary file's mode.
Confirmed on main ac9b5b8380517ded445b09dd3196d8d9e2ba3c59 with the existing local installer fixture on Linux, using its macOS mock. This does not establish behavior on an actual macOS host.
What steps can reproduce the bug?
Save and run python installer-profile-symlink-repro.py /path/to/codex-checkout. All package downloads are mocked by the existing local fixture; all profile writes remain in a temporary directory.
#!/usr/bin/env python3
"""Local installer fixture on Linux with mocked macOS; no network or user profile writes.
Usage: python installer-profile-symlink-repro.py /path/to/codex-checkout
"""
import importlib.util
from pathlib import Path
import stat
import sys
import tempfile
checkout = Path(sys.argv[1]).resolve()
spec = importlib.util.spec_from_file_location(
"installer_fixture", checkout / "scripts/install/test_install_sh.py"
)
fixture = importlib.util.module_from_spec(spec)
spec.loader.exec_module(fixture)
with tempfile.TemporaryDirectory(prefix="codex profile repro ") as temporary:
root = Path(temporary).resolve()
archive, checksum, metadata = fixture.create_package_release(root)
(root / "home").mkdir()
(root / "dotfiles").mkdir()
target = root / "dotfiles/profile"
profile = root / "home/.profile"
profile.symlink_to("../dotfiles/profile")
target.write_text(
'export EXISTING_SETTING="keep this"\n'
'# >>> Codex installer >>>\nexport PATH="/old/bin:$PATH"\n'
"# <<< Codex installer <<<\n"
)
target.chmod(0o640)
result, _ = fixture.run_installer_in(
root,
fixture.VERSION,
metadata_json=metadata,
archive_path=archive,
checksum_path=checksum,
force_macos=True,
)
print(f"installer_exit={result.returncode}")
print(f"profile_is_symlink={profile.is_symlink()}")
print(f"dotfiles_target_updated={str(root / 'install-bin') in target.read_text()}")
print(f"profile_mode={oct(stat.S_IMODE(profile.stat().st_mode))}")
print(f"dotfiles_target_mode={oct(stat.S_IMODE(target.stat().st_mode))}")
print(f"existing_setting_preserved={'keep this' in profile.read_text()}")
if result.stderr:
print(f"stderr={result.stderr.strip()}")
With the original installer:
installer_exit=0
profile_is_symlink=False
dotfiles_target_updated=False
profile_mode=0o600
dotfiles_target_mode=0o640
existing_setting_preserved=True
The resulting mode depends on the ambient umask; 0600 is the observed value here, not a universal value.
What is the expected behavior?
Updating the installer-managed PATH block should preserve the user's profile symlink, update its target, retain unrelated content, and preserve the existing profile permissions.
Additional information
rewrite_path_block() writes a temporary file and moves it over the profile pathname. The move replaces the symlink instead of following it, and the temporary file does not retain the original file's mode.
I tested a candidate that resolves the final profile target, stages the update beside that target while retaining its file metadata, and replaces the target instead of the profile symlink. The reproduction then preserves the symlink, updates the dotfiles target, and retains mode 0640. The complete installer suite passes 23 tests and 11 subtests; coverage includes linked profile chains, a regular profile's mode, first-time appends, first-time writes through a broken link whose target directory exists, and failure cleanup that leaves original content intact. Shell syntax and diff checks pass.
Searched installer/profile/symlink and dotfiles reports and found no equivalent issue. No external PR is requested, consistent with the contribution policy.
- Dominant language
- Rust
- Stars
- 128k
- Forks
- 20.1k
- Avg merge
- 1m
- Merged PRs (30d)
- 994
Getting set up
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from openai/codex
-
app enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Maintainers usually reply within 1 day
-
app bug config windows-os
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
openai/codex#51926 · 2 comments ·
Maintainers usually reply within 1 day
-
app bug model-behavior windows-os
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
openai/codex#51912 · 1 comment ·
Maintainers usually reply within 1 day
-
app bug dots remote
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
Maintainers usually reply within 1 day
-
app bug performance
Difficulty 2/5 1-3 hours Newbie friendliness 80/100
openai/codex#51818 · 1 comment ·
Maintainers usually reply within 1 day
Similar issues
-
Difficulty 1/5 Under an hour Newbie friendliness 75/100
ajeetraina/awesome-docker-sbx#220 ·
-
`helios / deploy`: switch zone wait in `deploy.sh` has almost no headroom over healthy startup timesOpenTest Flake
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
oxidecomputer/omicron#11453 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
Maintainers usually reply within 1 day
-
[Feature] 设置里面的同步功能Openenhancement user-priority/P2
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
Maintainers usually reply within 1 day
-
agent:triaged bug bughunt pm:npm priority:p1
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
SocketDev/socket-patch#1127 · 1 comment ·
Maintainers usually reply within 1 day