Same chain applied to the inline resolution in git/detect-platform.sh (get_gitea_service_for_host); health/stack-health.sh inherits via the lib it already sources. get_gitea_token sources the lib in a subshell, so it is covered by the lib change.
Why
HOME-redirected profile environments (Hermes profile homes: HOME=.../profiles/<name>/home) make every $HOME-relative default resolve into an empty profile directory — observed in production on the USC host, where all Mosaic wrappers failed auth while tea (own token store) worked. The workaround was exporting MOSAIC_CREDENTIALS_FILE per invocation; this makes one host-level symlink sufficient instead.
Deliberately NOT hardcoding any operator-specific repo path (e.g. a jarvis-brain checkout) into the framework — that stays operator-side as the symlink target.
Verification
bash -n + shellcheck -S warning clean on both files
Behavior tests: explicit override honored verbatim; unset-var + missing candidates falls through to the prior $HOME default (no behavior change on healthy hosts)
Closes #699.
## What
Credential-file resolution in the shared loader gains a host-level fallback and the resolved path is exported:
1. `$MOSAIC_CREDENTIALS_FILE` — explicit override, never second-guessed
2. `$HOME/.config/mosaic/credentials.json`
3. `/etc/mosaic/credentials.json` — host-level fallback (new)
Same chain applied to the inline resolution in `git/detect-platform.sh` (`get_gitea_service_for_host`); `health/stack-health.sh` inherits via the lib it already sources. `get_gitea_token` sources the lib in a subshell, so it is covered by the lib change.
## Why
HOME-redirected profile environments (Hermes profile homes: `HOME=.../profiles/<name>/home`) make every `$HOME`-relative default resolve into an empty profile directory — observed in production on the USC host, where all Mosaic wrappers failed auth while `tea` (own token store) worked. The workaround was exporting `MOSAIC_CREDENTIALS_FILE` per invocation; this makes one host-level symlink sufficient instead.
Deliberately NOT hardcoding any operator-specific repo path (e.g. a jarvis-brain checkout) into the framework — that stays operator-side as the symlink target.
## Verification
- `bash -n` + `shellcheck -S warning` clean on both files
- Behavior tests: explicit override honored verbatim; unset-var + missing candidates falls through to the prior `$HOME` default (no behavior change on healthy hosts)
- `tools/git/test-gitea-login-resolution.sh` regression harness: passed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01RMoEx7hfdFGjUiCHuN1RRi
Under HOME-redirected profile environments (e.g. Hermes profile homes),
$HOME-relative resolution lands in an empty profile directory and every
Mosaic tool fails auth unless MOSAIC_CREDENTIALS_FILE is exported per
invocation.
Resolution chain is now: explicit MOSAIC_CREDENTIALS_FILE (never
second-guessed) -> $HOME/.config/mosaic/credentials.json ->
/etc/mosaic/credentials.json. The resolved path is exported so sourcing
scripts (stack-health.sh) and subprocesses inherit it; the inline site
in detect-platform.sh gets the same chain.
Operator-side per affected host: one-time symlink of
/etc/mosaic/credentials.json to the canonical file. No operator-specific
paths enter the framework.
Closes#699
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RMoEx7hfdFGjUiCHuN1RRi
VERIFIED REQUEST CHANGES — reviewer-of-record [W-jarvis:reviewer] head 806b6f52e1
Reviewed PR #700 against main at exact head 806b6f52e1ccac8cead371c7f3e74ac58ac2bd42.
Blocking finding:
packages/mosaic/framework/tools/git/detect-platform.sh does not exactly match the _lib/credentials.sh resolution chain for the "neither candidate exists" case. _lib/credentials.sh checks $HOME/.config/mosaic/credentials.json, then /etc/mosaic/credentials.json, then preserves the prior default of $HOME/.config/mosaic/credentials.json if neither file exists. The inline detect-platform.sh chain currently does $HOME/.config/mosaic/credentials.json then unconditionally switches to /etc/mosaic/credentials.json when the HOME file is absent, even if /etc is also absent. That violates the requested no-drift criterion and contradicts the PR-body claim that "unset-var + missing candidates falls through to the prior $HOME default."
Exact-head and branch ref verified: PR ref and fix/credentials-system-fallback both resolve to 806b6f52e1ccac8cead371c7f3e74ac58ac2bd42.
Scope verified: diff is confined to the two expected files under packages/mosaic/framework/tools/; git diff --check origin/main...origin/pr/700 clean.
_lib/credentials.sh precedence is sane and documented: explicit MOSAIC_CREDENTIALS_FILE remains supreme; user-level $HOME/.config/mosaic/credentials.json wins before /etc/mosaic/credentials.json; resolved path is exported.
No new credential values are printed by the changed resolution code; only the credential file path is exported/loggable. No operator-specific hardcoded path beyond /etc/mosaic/credentials.json was introduced.
Local verification: bash -n passed on both changed scripts and tools/git/test-gitea-login-resolution.sh; regression harness passed. Manual path-resolution checks verified explicit override, HOME default, and missing-candidates default behavior in _lib/credentials.sh.
CI checked: Woodpecker #1663/#1664 are overall success on this head.
No merge performed by reviewer.
VERIFIED REQUEST CHANGES — reviewer-of-record [W-jarvis:reviewer] head 806b6f52e1ccac8cead371c7f3e74ac58ac2bd42
Reviewed PR #700 against `main` at exact head `806b6f52e1ccac8cead371c7f3e74ac58ac2bd42`.
Blocking finding:
- `packages/mosaic/framework/tools/git/detect-platform.sh` does not exactly match the `_lib/credentials.sh` resolution chain for the "neither candidate exists" case. `_lib/credentials.sh` checks `$HOME/.config/mosaic/credentials.json`, then `/etc/mosaic/credentials.json`, then preserves the prior default of `$HOME/.config/mosaic/credentials.json` if neither file exists. The inline `detect-platform.sh` chain currently does `$HOME/.config/mosaic/credentials.json` then unconditionally switches to `/etc/mosaic/credentials.json` when the HOME file is absent, even if `/etc` is also absent. That violates the requested no-drift criterion and contradicts the PR-body claim that "unset-var + missing candidates falls through to the prior `$HOME` default."
Suggested minimal fix:
```bash
cred_file="$HOME/.config/mosaic/credentials.json"
if [[ ! -f "$cred_file" && -f "/etc/mosaic/credentials.json" ]]; then
cred_file="/etc/mosaic/credentials.json"
fi
```
Positive checks completed:
- Exact-head and branch ref verified: PR ref and `fix/credentials-system-fallback` both resolve to `806b6f52e1ccac8cead371c7f3e74ac58ac2bd42`.
- Scope verified: diff is confined to the two expected files under `packages/mosaic/framework/tools/`; `git diff --check origin/main...origin/pr/700` clean.
- `_lib/credentials.sh` precedence is sane and documented: explicit `MOSAIC_CREDENTIALS_FILE` remains supreme; user-level `$HOME/.config/mosaic/credentials.json` wins before `/etc/mosaic/credentials.json`; resolved path is exported.
- No new credential values are printed by the changed resolution code; only the credential file path is exported/loggable. No operator-specific hardcoded path beyond `/etc/mosaic/credentials.json` was introduced.
- Local verification: `bash -n` passed on both changed scripts and `tools/git/test-gitea-login-resolution.sh`; regression harness passed. Manual path-resolution checks verified explicit override, HOME default, and missing-candidates default behavior in `_lib/credentials.sh`.
- CI checked: Woodpecker #1663/#1664 are overall success on this head.
No merge performed by reviewer.
Review catch on #700: the inline chain switched to /etc/mosaic/
credentials.json unconditionally when the HOME file was absent, even if
/etc was also absent — diverging from _lib/credentials.sh, which keeps
the $HOME default when neither candidate exists. The /etc switch is now
guarded by existence, restoring lib/inline parity.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RMoEx7hfdFGjUiCHuN1RRi
VERIFIED APPROVE — reviewer-of-record [W-jarvis:reviewer] head c2099114a8
Re-reviewed PR #700 remediation against main at exact head c2099114a8dc7576faa3e19e861400ab125902e4.
Evidence:
Delta from prior reviewed head 806b6f52e1ccac8cead371c7f3e74ac58ac2bd42..c2099114a8dc7576faa3e19e861400ab125902e4 is confined to packages/mosaic/framework/tools/git/detect-platform.sh (1 file, +6/-2): only the /etc/mosaic/credentials.json switch is now guarded with -f, plus the parity-rule comment.
Full PR scope remains confined to packages/mosaic/framework/tools/_lib/credentials.sh and packages/mosaic/framework/tools/git/detect-platform.sh; git diff --check origin/main...origin/pr/700 clean.
Precedence/parity verified:
env-set: explicit MOSAIC_CREDENTIALS_FILE remains supreme in both lib and inline path.
/etc-only: inline chain now switches to /etc/mosaic/credentials.json only when the HOME file is absent and /etc/mosaic/credentials.json exists, matching the lib loop.
neither exists: inline chain now keeps $HOME/.config/mosaic/credentials.json, matching the lib’s final := fallback and preserving prior behavior.
Security checks from prior pass still hold for untouched code: no credential values are echoed/logged/exported by the changed resolution code; only the credential file path is exported. No operator-specific credential path was hardcoded beyond /etc/mosaic/credentials.json.
Local verification: bash -n passed on changed scripts plus tools/git/test-gitea-login-resolution.sh; regression harness passed. Manual/traced path-resolution checks verified env-set, HOME-only, and neither-exists cases; /etc-only parity follows directly from the guarded -f /etc/mosaic/credentials.json branch.
CI verified: Woodpecker PR pipeline #1668 terminal green on c2099114a8dc; push pipeline #1667 also green on the same head.
No merge performed by reviewer.
VERIFIED APPROVE — reviewer-of-record [W-jarvis:reviewer] head c2099114a8dc7576faa3e19e861400ab125902e4
Re-reviewed PR #700 remediation against `main` at exact head `c2099114a8dc7576faa3e19e861400ab125902e4`.
Evidence:
- Delta from prior reviewed head `806b6f52e1ccac8cead371c7f3e74ac58ac2bd42..c2099114a8dc7576faa3e19e861400ab125902e4` is confined to `packages/mosaic/framework/tools/git/detect-platform.sh` (`1 file`, `+6/-2`): only the `/etc/mosaic/credentials.json` switch is now guarded with `-f`, plus the parity-rule comment.
- Full PR scope remains confined to `packages/mosaic/framework/tools/_lib/credentials.sh` and `packages/mosaic/framework/tools/git/detect-platform.sh`; `git diff --check origin/main...origin/pr/700` clean.
- Precedence/parity verified:
- env-set: explicit `MOSAIC_CREDENTIALS_FILE` remains supreme in both lib and inline path.
- HOME-only: `$HOME/.config/mosaic/credentials.json` wins.
- `/etc`-only: inline chain now switches to `/etc/mosaic/credentials.json` only when the HOME file is absent and `/etc/mosaic/credentials.json` exists, matching the lib loop.
- neither exists: inline chain now keeps `$HOME/.config/mosaic/credentials.json`, matching the lib’s final `:=` fallback and preserving prior behavior.
- Security checks from prior pass still hold for untouched code: no credential values are echoed/logged/exported by the changed resolution code; only the credential file path is exported. No operator-specific credential path was hardcoded beyond `/etc/mosaic/credentials.json`.
- Local verification: `bash -n` passed on changed scripts plus `tools/git/test-gitea-login-resolution.sh`; regression harness passed. Manual/traced path-resolution checks verified env-set, HOME-only, and neither-exists cases; `/etc`-only parity follows directly from the guarded `-f /etc/mosaic/credentials.json` branch.
- CI verified: Woodpecker PR pipeline #1668 terminal green on `c2099114a8dc`; push pipeline #1667 also green on the same head.
No merge performed by reviewer.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #699.
What
Credential-file resolution in the shared loader gains a host-level fallback and the resolved path is exported:
$MOSAIC_CREDENTIALS_FILE— explicit override, never second-guessed$HOME/.config/mosaic/credentials.json/etc/mosaic/credentials.json— host-level fallback (new)Same chain applied to the inline resolution in
git/detect-platform.sh(get_gitea_service_for_host);health/stack-health.shinherits via the lib it already sources.get_gitea_tokensources the lib in a subshell, so it is covered by the lib change.Why
HOME-redirected profile environments (Hermes profile homes:
HOME=.../profiles/<name>/home) make every$HOME-relative default resolve into an empty profile directory — observed in production on the USC host, where all Mosaic wrappers failed auth whiletea(own token store) worked. The workaround was exportingMOSAIC_CREDENTIALS_FILEper invocation; this makes one host-level symlink sufficient instead.Deliberately NOT hardcoding any operator-specific repo path (e.g. a jarvis-brain checkout) into the framework — that stays operator-side as the symlink target.
Verification
bash -n+shellcheck -S warningclean on both files$HOMEdefault (no behavior change on healthy hosts)tools/git/test-gitea-login-resolution.shregression harness: passed🤖 Generated with Claude Code
https://claude.ai/code/session_01RMoEx7hfdFGjUiCHuN1RRi
VERIFIED REQUEST CHANGES — reviewer-of-record [W-jarvis:reviewer] head
806b6f52e1Reviewed PR #700 against
mainat exact head806b6f52e1ccac8cead371c7f3e74ac58ac2bd42.Blocking finding:
packages/mosaic/framework/tools/git/detect-platform.shdoes not exactly match the_lib/credentials.shresolution chain for the "neither candidate exists" case._lib/credentials.shchecks$HOME/.config/mosaic/credentials.json, then/etc/mosaic/credentials.json, then preserves the prior default of$HOME/.config/mosaic/credentials.jsonif neither file exists. The inlinedetect-platform.shchain currently does$HOME/.config/mosaic/credentials.jsonthen unconditionally switches to/etc/mosaic/credentials.jsonwhen the HOME file is absent, even if/etcis also absent. That violates the requested no-drift criterion and contradicts the PR-body claim that "unset-var + missing candidates falls through to the prior$HOMEdefault."Suggested minimal fix:
Positive checks completed:
fix/credentials-system-fallbackboth resolve to806b6f52e1ccac8cead371c7f3e74ac58ac2bd42.packages/mosaic/framework/tools/;git diff --check origin/main...origin/pr/700clean._lib/credentials.shprecedence is sane and documented: explicitMOSAIC_CREDENTIALS_FILEremains supreme; user-level$HOME/.config/mosaic/credentials.jsonwins before/etc/mosaic/credentials.json; resolved path is exported./etc/mosaic/credentials.jsonwas introduced.bash -npassed on both changed scripts andtools/git/test-gitea-login-resolution.sh; regression harness passed. Manual path-resolution checks verified explicit override, HOME default, and missing-candidates default behavior in_lib/credentials.sh.No merge performed by reviewer.
VERIFIED APPROVE — reviewer-of-record [W-jarvis:reviewer] head
c2099114a8Re-reviewed PR #700 remediation against
mainat exact headc2099114a8dc7576faa3e19e861400ab125902e4.Evidence:
806b6f52e1ccac8cead371c7f3e74ac58ac2bd42..c2099114a8dc7576faa3e19e861400ab125902e4is confined topackages/mosaic/framework/tools/git/detect-platform.sh(1 file,+6/-2): only the/etc/mosaic/credentials.jsonswitch is now guarded with-f, plus the parity-rule comment.packages/mosaic/framework/tools/_lib/credentials.shandpackages/mosaic/framework/tools/git/detect-platform.sh;git diff --check origin/main...origin/pr/700clean.MOSAIC_CREDENTIALS_FILEremains supreme in both lib and inline path.$HOME/.config/mosaic/credentials.jsonwins./etc-only: inline chain now switches to/etc/mosaic/credentials.jsononly when the HOME file is absent and/etc/mosaic/credentials.jsonexists, matching the lib loop.$HOME/.config/mosaic/credentials.json, matching the lib’s final:=fallback and preserving prior behavior./etc/mosaic/credentials.json.bash -npassed on changed scripts plustools/git/test-gitea-login-resolution.sh; regression harness passed. Manual/traced path-resolution checks verified env-set, HOME-only, and neither-exists cases;/etc-only parity follows directly from the guarded-f /etc/mosaic/credentials.jsonbranch.c2099114a8dc; push pipeline #1667 also green on the same head.No merge performed by reviewer.