Skip to content

#1897 fix: resolve hyde paths without erroring on an unset environment - #1900

Merged
kRHYME7 merged 4 commits into
HyDE-Project:devfrom
RAprogramm:1897
Jul 30, 2026
Merged

#1897 fix: resolve hyde paths without erroring on an unset environment#1900
kRHYME7 merged 4 commits into
HyDE-Project:devfrom
RAprogramm:1897

Conversation

@RAprogramm

@RAprogramm RAprogramm commented Jul 30, 2026

Copy link
Copy Markdown

Refs #1897

Problem

hyde/path.lua is the first thing the Hyprland Lua configuration loads, and it resolved every directory by concatenating an environment variable:

P.config = os.getenv("XDG_CONFIG_HOME") or (os.getenv("HOME") .. "/.config")

Two ways that fails on a session the compositor is trying to start:

  • HOME absent β€” the fallback raises attempt to concatenate a nil value and the configuration dies before the first window. Same expression in the lib and share candidate lists.
  • The variable set but empty β€” os.getenv returns "", which is truthy in Lua, so P.config becomes "" and every path built from it points at the filesystem root. The XDG specification says an empty value is to be treated as unset; the repository's own luautils/xdg.lua already does that.

dynamic.lua then repeated the first mistake directly:

local user_config_path = os.getenv("XDG_CONFIG_HOME") .. "/hyde/config.toml"
if io.open(user_config_path) then

That is the path a session started from a TTY takes, without uwsm to export the variable. The io.open probe also never closed its handle, leaking one on every configuration reload.

Change

hyde/path.lua resolves through one helper that treats unset and empty alike and returns nil when HOME cannot supply a fallback. A field that cannot be resolved stays nil for the caller to skip β€” raising in this module takes the whole configuration down, which is the failure being fixed. The directory probe closes its pipe, and the lib and share lookups drop the user candidate instead of concatenating onto an absent HOME. The module and its helpers are documented.

dynamic.lua reads the directory from hyde.path and closes the handle it opens.

XDG_RUNTIME_DIR deliberately keeps no fallback: the specification puts it on the session to provide, and inventing a path would place runtime state somewhere that outlives the session.

Tests

tests/test_paths.sh (new) checks that an empty XDG_CONFIG_HOME falls back to HOME, that a set one is honoured, and that an absent HOME leaves the field unresolved rather than raising. It also asserts dynamic.lua neither rebuilds the path from os.getenv nor probes a file without closing it.

Verified against the previous revision of each file: restoring the old resolver fails the suite with attempt to concatenate a nil value, and restoring the old dynamic.lua fails both of its assertions.

Full suite: 10 cases, 0 failed.

Scope

This is not a fix for the timeout reported in #1897 β€” the measurements in that thread put the cost in the size of the file being parsed. These are two defects found in the same two lines while reading it.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Checklist

  • I have read the CONTRIBUTING document.
  • My code follows the code style of this project.
  • My commit message follows the commit guidelines.
  • I have added a changelog entry.
  • I have added necessary comments/documentation to my code.
  • I have added tests to cover my changes.
  • I have tested my code locally and it works as expected.
  • All new and existing tests passed.

Summary by CodeRabbit

  • Bug Fixes

    • Hyprland sessions now start correctly when XDG_CONFIG_HOME or HOME is unavailable.
    • Optional user configuration files are skipped safely when paths cannot be resolved or files do not exist.
    • Improved handling of XDG configuration, cache, state, data, library, and shared directories.
  • Tests

    • Added coverage for missing environment variables, fallback paths, and optional configuration loading.
  • Documentation

    • Documented the fix in the unreleased changelog.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. πŸŽ‰

ℹ️ Recent review info
βš™οΈ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5a7b50c5-7aaa-409c-939c-51ffbde5decf

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 8f0b9c4 and ea3988d.

πŸ“’ Files selected for processing (4)
  • CHANGELOG.md
  • Configs/.local/share/hypr/lua/dynamic.lua
  • Configs/.local/share/hypr/lua/hyde/path.lua
  • tests/test_paths.sh
🚧 Files skipped from review as they are similar to previous changes (4)
  • Configs/.local/share/hypr/lua/dynamic.lua
  • tests/test_paths.sh
  • CHANGELOG.md
  • Configs/.local/share/hypr/lua/hyde/path.lua

πŸ“ Walkthrough

Walkthrough

The Lua path module safely resolves missing XDG variables, dynamic configuration loading skips unavailable paths, file probes close handles, and shell tests cover configured, fallback, and unset-variable behavior.

Changes

XDG path resolution and configuration loading

Layer / File(s) Summary
Safe XDG and directory resolution
Configs/.local/share/hypr/lua/hyde/path.lua
XDG paths tolerate missing variables, expose P.data, preserve an optional runtime path, and safely probe user and system directories.
Conditional user configuration loading
Configs/.local/share/hypr/lua/dynamic.lua
User configuration loads only when the resolved config path and file are available, with probe handles closed.
Path regression coverage and changelog
tests/test_paths.sh, CHANGELOG.md
Shell tests cover configured, fallback, and absent environment variables; the fix is documented as unreleased.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Poem

I’m a bunny guarding paths tonight,
No missing home can cause a fright.
Configs peek, then close the door,
Safe little probes leave leaks no more.
Hop, hopβ€”Hyprland starts just right!

πŸš₯ Pre-merge checks | βœ… 5
βœ… Passed checks (5 passed)
Check name Status Explanation
Description Check βœ… Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check βœ… Passed The title clearly and concisely describes the main fix: resolving HyDE paths safely when environment variables are unset.
Docstring Coverage βœ… Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check βœ… Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check βœ… Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❀️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

πŸ€– Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Configs/.local/share/hypr/lua/hyde/path.lua`:
- Around line 64-65: Update is_directory to safely shell-quote the path before
interpolating it into the io.popen test command, preserving correct directory
detection for paths containing apostrophes and preventing injected shell syntax
from executing.
- Around line 51-54: Update the P.runtime initialization in path.lua to convert
an empty XDG_RUNTIME_DIR value to nil while preserving non-empty values and
unset-variable behavior. Add a regression test covering an explicitly empty
XDG_RUNTIME_DIR and verify the unresolved runtime path remains nil.
πŸͺ„ Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
βš™οΈ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3caf498c-4548-4d1b-bec8-bf06dcdaa47d

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 8f0b9c4 and 21b5e98.

πŸ“’ Files selected for processing (4)
  • CHANGELOG.md
  • Configs/.local/share/hypr/lua/dynamic.lua
  • Configs/.local/share/hypr/lua/hyde/path.lua
  • tests/test_paths.sh

Comment on lines +51 to 54
--- @field runtime string|nil Runtime directory. It has no fallback: the spec
--- requires the session to provide one, and inventing a path would put runtime
--- state somewhere that outlives the session.
P.runtime = os.getenv("XDG_RUNTIME_DIR")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟑 Minor | ⚑ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/sh
# Statically verify that empty runtime values are normalized and covered.
rg -n -C 2 'P\.runtime|XDG_RUNTIME_DIR' \
  Configs/.local/share/hypr/lua/hyde/path.lua tests/test_paths.sh

Repository: HyDE-Project/HyDE

Length of output: 602


🏁 Script executed:

#!/bin/sh
set -eu
echo "== file excerpt =="
sed -n '1,120p' Configs/.local/share/hyperpr/lua/hyde/path.lua 2>/dev/null || sed -n '1,140p' Configs/.local/share/hypr/lua/hyde/path.lua

echo "== consumers of P.runtime =="
rg -n 'P\.runtime|runtime' Configs/.local/share/hypr/lua/hyde -S || true
rg -n 'P\.runtime|runtime' Configs tests -S || true

echo "== tests around path/env =="
rg -n -C 3 'XDG_RUNTIME_DIR|runtime|path' tests Configs/.local/share/hypr/lua/hyde -S || true

echo "== Lua truthiness probe =="
lua5.1 - <<'LUA' 2>/dev/null || lua - <<'LUA' 2>/dev/null || true
local runtime = ""
print("runtime=" .. runtime)
print("runtime truthy=" .. tostring(runtime and "yes" or "no"))
LUA

Repository: HyDE-Project/HyDE

Length of output: 29570


🏁 Script executed:

#!/bin/sh
set -eu
echo "== file excerpt =="
sed -n '1,160p' Configs/.local/share/hypr/lua/hyde/path.lua

echo "== consumers of P.runtime =="
rg -n 'P\.runtime|runtime' Configs/.local/share/hypr/lua/hyde -S || true
rg -n 'P\.runtime|runtime' Configs tests -S || true

echo "== Lua truthiness probe =="
lua5.1 - <<'LUA' 2>/dev/null || lua - <<'LUA' 2>/dev/null || true
local runtime = ""
print("runtime=" .. runtime)
print("runtime truthy=" .. tostring(runtime and "yes" or "no"))
LUA

Repository: HyDE-Project/HyDE

Length of output: 4571


🏁 Script executed:

#!/bin/sh
set -eu
echo "== hyde/runtime-related occurrences across repo =="
rg -n -C 3 'hyde\.path\.runtime|XDG_RUNTIME_DIR|P\.runtime|runtime' Configs tests -S \
  -g '!test_screenshot_wrapper.sh' || true

echo "== path module direct runtime tests =="
python3 - <<'PY'
from pathlib import Path
p = Path("tests/test_paths.sh")
text = p.read_text() if p.exists() else ""
for line in text.splitlines():
    if "XDG_RUNTIME_DIR" in line or 'P.runtime' in line:
        print(line)
PY

echo "== Lua empty-string truthiness probe =="
lua - <<'LUA' 2>/dev/null || true
local runtime = ""
print("runtime=" .. runtime)
print("runtime truthy=" .. tostring(runtime and "yes" or "no"))
LUA

Repository: HyDE-Project/HyDE

Length of output: 313


🏁 Script executed:

#!/bin/sh
set -eu
echo "== hyde/runtime-related occurrences across repo =="
rg -n -C 3 'hyde\.path\.runtime|XDG_RUNTIME_DIR|P\.runtime|runtime' Configs tests -S \
  -g '!test_screenshot_wrapper.sh' || true

echo "== path module direct runtime tests =="
python3 <<'PY'
from pathlib import Path
p = Path("tests/test_paths.sh")
text = p.read_text() if p.exists() else ""
for line in text.splitlines():
    if "XDG_RUNTIME_DIR" in line or 'P.runtime' in line:
        print(line)
PY

echo "== Lua empty-string truthiness probe =="
lua - <<'LUA' 2>/dev/null || true
local runtime = ""
print("runtime=" .. runtime)
print("runtime truthy=" .. tostring(runtime and "yes" or "no"))
LUA

Repository: HyDE-Project/HyDE

Length of output: 313


Normalize an empty runtime directory to nil.

P.runtime = os.getenv("XDG_RUNTIME_DIR") leaves an empty XDG_RUNTIME_DIR as "", a truthy Lua value that violates the module’s β€œunresolved paths are nil” contract for runtime state. Treat an empty runtime as unset, and add a regression case for this input.

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Configs/.local/share/hypr/lua/hyde/path.lua` around lines 51 - 54, Update the
P.runtime initialization in path.lua to convert an empty XDG_RUNTIME_DIR value
to nil while preserving non-empty values and unset-variable behavior. Add a
regression test covering an explicitly empty XDG_RUNTIME_DIR and verify the
unresolved runtime path remains nil.

Comment on lines +64 to +65
local function is_directory(path)
local pipe = io.popen("[ -d '" .. path .. "' ] && echo 1 || echo 0")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ”’ Security & Privacy | 🟠 Major | ⚑ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/sh
# Inspect shell command construction without executing repository files.
rg -n -C 3 'io\.popen|shell_quote|is_directory' \
  Configs/.local/share/hypr/lua/hyde/path.lua

Repository: HyDE-Project/HyDE

Length of output: 901


🏁 Script executed:

#!/bin/sh
set -eu

echo "== path.lua relevant section =="
sed -n '1,130p' Configs/.local/share/hypr/lua/hyde/path.lua

echo
echo "== is_directory call sites / exposed API =="
rg -n -C 2 'is_directory|first_directory|os\.getenv|XDG|HOME' Configs/.local/share/hypr/lua/hyde/path.lua

echo
echo "== shell quoting probe without repository code =="
python3 - <<'PY'
def current(path):
    return f"[ -d '{path}' ] && echo 1 || echo 0"

examples = [
    "/home/alice",
    "/home/alice's project",
    "/home/alice; rm -rf /",
    "/home/alice'\"; echo PWNED; #",
]
for path in examples:
    cmd = current(path)
    print(repr(path))
    print(cmd)
    print()
PY

Repository: HyDE-Project/HyDE

Length of output: 7346


Shell-quote paths before probing them.

is_directory() interpolates HOME/XDG values into a shell command without escaping. A path containing ' breaks quoting; if HOME or an XDG directory is set maliciously, it can inject shell syntax while Hyprland reads its configuration.

Proposed fix
+local function shell_quote(value)
+    return "'" .. value:gsub("'", "'\\''") .. "'"
+end
+
 local function is_directory(path)
-    local pipe = io.popen("[ -d '" .. path .. "' ] && echo 1 || echo 0")
+    local pipe = io.popen("[ -d " .. shell_quote(path) .. " ] && echo 1 || echo 0")
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
local function is_directory(path)
local pipe = io.popen("[ -d '" .. path .. "' ] && echo 1 || echo 0")
local function shell_quote(value)
return "'" .. value:gsub("'", "'\\''") .. "'"
end
local function is_directory(path)
local pipe = io.popen("[ -d " .. shell_quote(path) .. " ] && echo 1 || echo 0")
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Configs/.local/share/hypr/lua/hyde/path.lua` around lines 64 - 65, Update
is_directory to safely shell-quote the path before interpolating it into the
io.popen test command, preserving correct directory detection for paths
containing apostrophes and preventing injected shell syntax from executing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants