Skip to content

Session: the runtime directory is a last-resort save candidate on device too - #185

Merged
GLinnik21 merged 1 commit into
mainfrom
fix/runtime-dir-session-fallback
Sep 21, 2026
Merged

GLinnik21 merged 1 commit into
mainfrom
fix/runtime-dir-session-fallback

Conversation

@GLinnik21

Copy link
Copy Markdown
Owner

A reporter on an unrooted LG webOS 4.4.3 set signs in successfully and the session is never saved:
session: could not persist to ANY candidate path. On a device install session_candidates()
offered exactly three paths — /media/developer/<id>-auth.json, /media/internal/.<id>-auth.json
and in_app_dir("auth.json"). /media/internal is mount ro in LG's stock jail profile, so it can
never succeed; the other two are refused on his television.

There is a fourth path we already know is writable there, and we know it from his own report: his
event log sits at /tmp/com.beb.plxnative.nightly/plxnative-events.log. The jail mounts /tmp rw at
1777 and the per-install runtime root is app-owned. in_runtime_dir("auth.json") was already a
candidate — but only under ENV_STEERABLE, so a shipped install never got it.

It is offered on a device install now, strictly last, after every durable path. An install whose
jail path is writable — today's ordinary case — sees no change at all. A steerable build keeps that
same path FIRST for its own unrelated reason (concurrent simulators must not share one session file)
and is guarded against receiving it twice, which would otherwise put two entries in the list
resolving to one file.

This is deliberately a partial fix, and the code says so rather than pretending otherwise: a save
that lands only in the runtime directory is reported as the non-durable outcome it is. The sign-in
survives leaving and reopening the app; it does not survive restarting the television. Telling the
user "saved" would be a lie, and the existing CanonicalCommit/CompletionOutcome vocabulary
already had the right word for it.

The file holds an account token, every per-(user, server) PMS token and the Plex Home roster, and
/tmp is a bind mount of the HOST /tmp shared across jails, so the directory is listable by other
users on the set. The generic write path's protections therefore have to hold on this candidate
specifically, and are now tested there: mode 0600 asserted on the written file, and write_atomic
proven to refuse a symlink planted at the candidate.

Tests: the fallback is last and not duplicated on a device install; every durable candidate
unwritable plus a writable runtime directory persists and reloads across a simulated process
restart; the same shape with no fallback offered still fails (negative control); an uncertain
canonical commit never reports durable even when the fallback succeeded.

Does not fix the ~48 s main-thread freeze after Continue, and does not explain why the durable
paths are refused — that diagnostic is a separate change.

🤖 Generated with Claude Code

…ice too

A 2026-09-20 report from an unrooted webOS 4.4.3 set (Dev Mode, no ssh) signed
in successfully and then logged "session: could not persist to ANY candidate
path": none of /media/developer, /media/internal or the app dir accepted the
write on that jail. The one path known writable on that exact television was
the runtime root under /tmp — the event log was reaching it — but
in_runtime_dir("auth.json") was only ever offered as a session candidate
under ENV_STEERABLE (the simulator), so a real device install had nothing
left to try.

paths::session_candidates() now offers the runtime-dir path on a device
install too, strictly LAST — after every durable candidate, so an install
whose durable jail path is writable (the ordinary case) is unaffected. A
save that lands only there is not durable (/tmp is swept on reboot); it is
already reported through the same non-durable Uncertain class an unconfirmed
canonical commit uses, never as Durable, whichever legacy candidate the
write actually lands on (pinned by a new async_persistence test alongside
the two existing siblings for ProtectionFailed/Failed).

Regression coverage: paths::tests proves session_candidates() actually
offers the fallback last (RED before the fix — confirmed by reverting just
the production line); session_persistence_tests exercises the real
save_legacy_fallback_locked/read_legacy_locked loops against a candidate
list shaped like the field jail (two unwritable "durable" dirs, one
writable "runtime" dir) via a new TEST_CANDIDATES fixture, proving the
session persists at mode 0600 and reloads from disk across a simulated
process restart; a further test proves write_atomic refuses a symlink
planted at the runtime-dir candidate's own name, since /tmp is a
world-writable bind mount shared across jails.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 21, 2026 01:17

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-21T01:21:25.825608Z cb4d008 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cb4d008862

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread rust-modules/src/paths.rs
Comment on lines +516 to +517
if !ENV_STEERABLE {
v.push(in_runtime_dir("auth.json"));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Prevent stale durable candidates from shadowing the fallback

When an earlier candidate still contains a readable old session but its directory has become unwritable, this addition lets the plaintext save fall through successfully to the runtime file, but save_legacy_fallback_locked returns without removing the stale file while read_legacy_locked always accepts the first parsable candidate. After relaunch, the old higher-priority account/profile is therefore loaded instead of the newly written runtime session. This can occur with the explicitly read-only legacy app-dir migration source or after jail/mount permissions change, so a fallback success must retire or otherwise supersede readable earlier candidates.

Useful? React with 👍 / 👎.

Comment on lines +112 to +113
std::fs::set_permissions(&durable_a, std::fs::Permissions::from_mode(0o500)).unwrap();
std::fs::set_permissions(&durable_b, std::fs::Permissions::from_mode(0o500)).unwrap();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Make unwritable-candidate tests work when run as root

In root-run containers or CI jobs with CAP_DAC_OVERRIDE, mode 0500 does not prevent creating files in these directories, so the negative test writes its first candidate and fails, while the positive test never reaches the runtime candidate and also fails. Model refusal through a fault seam or a structurally unavailable/read-only location rather than Unix permission bits so the new regression tests remain valid under the repository's root development environments.

Useful? React with 👍 / 👎.

@GLinnik21
GLinnik21 merged commit 4f1c787 into main Sep 21, 2026
5 checks passed
@GLinnik21
GLinnik21 deleted the fix/runtime-dir-session-fallback branch September 21, 2026 01:31
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