Skip to content

fix(pty): self-heal a reaped shell-integration directory - #57

Merged
simota merged 2 commits into
mainfrom
fix/shell-integration-self-heal
Aug 4, 2026
Merged

fix(pty): self-heal a reaped shell-integration directory#57
simota merged 2 commits into
mainfrom
fix/shell-integration-self-heal

Conversation

@simota

@simota simota commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Summary

The shell-integration scripts are materialized once into
$TMPDIR/noa-shell-integration-<pid> and the path was then cached in a
OnceLock and handed out unchecked forever. When the OS temp reaper removes
that tree under a running noa, every later pane still gets the stale path —
and because ZDOTDIR (zsh) and --rcfile (bash) suppress the shell's normal
startup lookup, the shell comes up with no configuration at all, not even the
user's own. It reads as "zsh stopped loading and I got sh".

The scripts are now re-verified on every handout and rewritten when any went
missing, so an unusable directory degrades to "no integration" (user config
intact) rather than to a dangling path. Materialization failure is no longer
cached either: one transient write error used to disable integration for the
rest of the process's life. A second commit sweeps the per-launch directories
earlier runs left behind (112 had accumulated locally); a still-running process
whose directory is swept simply re-materializes it.

Test plan

  • cargo test -p noa-pty — 39 passed (sandbox off; openpty needs real devices)
  • New cases: full reap, single-file reap, sweep safety
  • cargo clippy -p noa-pty --all-targets and cargo fmt --all -- --check clean
  • Checked by hand that ZDOTDIR=/nonexistent zsh -l loads no user config

Not reproduced live in the reaped state — the root-cause link rests on that
shell-behavior check plus the leaked-directory evidence.

https://claude.ai/code/session_01CYRbmrCgjVLcVNasPyHYKX

simota added 2 commits August 4, 2026 10:47
The integration scripts are materialized once into
$TMPDIR/noa-shell-integration-<pid> and the path was then cached in a
OnceLock and handed out unchecked forever. When the OS temp reaper removes
that tree under a running noa, every later pane still gets the stale path —
and since ZDOTDIR (zsh) and --rcfile (bash) suppress the shell's normal
startup lookup, the shell comes up with no configuration at all, not even
the user's own. It reads as "zsh stopped loading and I got sh".

Re-verify the scripts on every handout and rewrite them when any went
missing, so an unusable directory degrades to "no integration" (user config
intact) rather than to a dangling path. Materialization failure is no longer
cached either: one transient write error used to disable integration for the
rest of the process's life.
One directory accumulates per launch and nothing but the OS temp reaper ever
removes them (112 had piled up locally). Sweep the siblings left by earlier
processes when this process picks its own directory, touching only trees idle
for a week; a still-running process whose directory is swept re-materializes
it on its next spawn.

@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: 8af627520e

ℹ️ 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 on lines +94 to +96
EMBEDDED_SCRIPTS
.iter()
.all(|(rel, _)| base.join(rel).is_file())

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 Verify script contents before accepting the cached tree

When a previous process's directory survives until its PID is reused, this check accepts that process's potentially older embedded scripts without rewriting them. It also accepts a truncated script left behind when std::fs::write created the file but returned an error during a prior repair. In either case zsh or bash can load stale or incomplete startup code and omit the user's configuration; compare each file with its embedded contents or unconditionally refresh the tree on first use.

Useful? React with 👍 / 👎.

Comment on lines +134 to +136
.file_name()
.and_then(|name| name.to_str())
.is_some_and(|name| name.starts_with(prefix))

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 Restrict sweeping to numeric PID directory names

When $TMPDIR contains any week-old directory whose name merely begins with noa-shell-integration-—for example noa-shell-integration-backup—this predicate classifies it as a prior generation and the code recursively deletes it. Since actual generated names have a nonempty numeric PID suffix, validate that complete suffix before allowing remove_dir_all to avoid deleting unrelated data.

Useful? React with 👍 / 👎.

Comment on lines +82 to +83
if materialize(base) {
return Some(base.as_path());

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 Serialize concurrent repairs of the shared script tree

When two threads call Pty::spawn after the tree has been reaped, both can observe missing scripts and enter materialize concurrently because only initialization of the path is synchronized. Each std::fs::write truncates the shared file, so one caller can return and start zsh or bash while the other caller is still truncating and rewriting its bootstrap scripts, causing that shell to read partial startup code; protect repair with a lock or publish atomically written replacements.

Useful? React with 👍 / 👎.

@simota
simota merged commit 1fac61b into main Aug 4, 2026
1 check passed
@simota
simota deleted the fix/shell-integration-self-heal branch August 4, 2026 02:16
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.

1 participant