Skip to content

[3381 by Claude Opus 5.5] Make 4 test files robust on the self-hosted nightly runner, without weakening what they verify. Check current upstream main first. - #3383

Merged
integry merged 2 commits into
mainfrom
3381/claude-opus-5-5-make-4-test-files-robust--20261029-1224-8k2
Oct 29, 2026
Merged

integry merged 2 commits into
mainfrom
3381/claude-opus-5-5-make-4-test-files-robust--20261029-1224-8k2

Conversation

@propr-dev

@propr-dev propr-dev Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

AI Implementation Summary

Closes #3381

Branch: 3381/claude-opus-5-5-make-4-test-files-robust--20261029-1224-8k2
Commits: ✅ Changes committed (9de0aba)


AI Processing Completed

Execution Details:

Summary:
I've fixed all 4 test files without changing production code, but I couldn't do the real-root check the issue asks for. This container has no Docker, sudo or user namespaces, so that run still needs a disposable root container. Upstream main was the same commit as this branch, so nothing there already covered it.

What was wrong and the fix

agentDefaultConfigPaths: the test sets HOME=/root. At ba1e272 (the nightly's commit), the task args mounted /root/.claude.json from that HOME, which only exists on the root host. Current main writes that mount differently (--mount type=bind,source=...), so the old check (no argument starts with /root/.claude) passes again but no longer catches anything. The test now sets CLAUDE_JSON_PATH to a temp .claude.json. It checks that this fixture is the mounted file and that no argument contains /root/.claude anywhere.

agentToolPolicyRuntime: the generated MCP config starts the local adapter with the image's /usr/local/bin/node. The test already redirected the adapter script to the repo copy, but not that Node path. If /usr/local/bin/node is missing on the runner (my guess, since the nightly uses setup-node), the adapter never starts. When I pointed that path at a missing binary, I got exactly the nightly failure: the same 18-tool list and both failing tests. The test now uses the test's own Node (process.execPath), and the CLI's XDG config/cache dirs sit inside the temp HOME.

followupHandoffReviewBlockers (F23): as root, chmod 000 can't produce EACCES. The test now injects EACCES, EPERM and EIO into the access check, so it runs under every uid. The real-chmod check is a separate test, skipped only when process.getuid?.() === 0, with that reason in the skip message. To prove it still catches problems, I temporarily made the code treat EACCES as "missing": the injected test failed even with the uid faked to 0.

agentRunAdmission (180 s timeout): the cause was a hang, not CPU. Two problems:

  • The file never exited. Importing the Docker executor opens the database connection, and nothing closed it. GitHub-hosted runners don't have redis-server, so those tests are skipped there and the file finishes in about 1 s. The self-hosted host has Redis, so they run and the file hung until the 180 s limit. It now closes the connection in after().
  • The 12-process case failed intermittently (2 of 3 full runs here). The worker fixture exited 500 ms after finishing, sometimes before its Redis slot was released. It now waits until Redis no longer holds a slot for its containers (up to 30 s), and fails if the slot is never released.

Verification

  • Normal user: all 4 files pass, with a locally built redis-server so the 25 Redis tests actually run. agentRunAdmission takes 70–83 s, including under a 2-CPU limit to mimic the runner's quota.
  • Closest to root I could get: all 4 pass with process.getuid() faked to 0 (the F23 skip shows its reason) and a fake populated agent home: .claude.json with an extra MCP server, .claude/settings.json, .credentials.json, .codex/config.toml. The host-style variables (CLAUDE_JSON_PATH, CLAUDE_CONFIG_DIR, CODEX_HOME, XDG_CONFIG_HOME, HOST_CLAUDE_DIR) were set as well.
  • Lint: npm run lint -- --max-warnings 0 passes. Root test/ isn't covered by ESLint or the typecheck gate, so I ran tsc on the 5 changed files: they have the same 4 type errors as before, and none are new.

A possible product issue, left unchanged

When the backend runs in a container and CLAUDE_JSON_PATH isn't set, the code falls back to .claude.json in the backend's own home. That's the same "uses the backend HOME" pattern the config-path code refuses for the credentials folder. The code comment says this is intended for dev/compose setups and it was only just reworked, so I didn't change it. It may be worth deciding separately whether it should also refuse.

Changed files: the 4 test files plus test/fixtures/agent-admission/worker.ts. There's nothing visual, so no previews.

Detailed Logs:

  • Conversation: 271 messages
  • Session: 1ad0fb67-46c0-4ecd-a0fe-201f6c3a3bbf

Log files stored at:

  • conversation: /usr/src/app/logs/executions/issue-3381-2026-10-29T13-10-18-700Z-conversation.json
  • output: /usr/src/app/logs/executions/issue-3381-2026-10-29T13-10-18-700Z-output.txt
Latest Conversation Messages
ASSISTANT: [content unavailable]

ASSISTANT: I've fixed all 4 test files without changing production code, but I couldn't do the real-root check the issue asks for. This container has no Docker, sudo or user namespaces, so that run still needs a...


This PR was created automatically by ProPR after processing issue #3381.


💡 Need changes?

Comment on this PR to request refinements — the AI agent monitors comments and will update the implementation based on your feedback. Keep iterating until you're satisfied!

…ouldn't do the real-root check the issue asks for. This container has no Docker, sudo or user namespaces, so that run still needs a disposable root container. Upstream `main` was the same commit as this branch, so nothing there already covered it.

What was wrong and the fix

`agentDefaultConfigPaths`: the test sets `HOME=/root`. At ba1e272 (the nightly's commit), the task args mounted `/root/.claude.json` from that HOME, which only exists on the root host. Current main writes that mount differently (`--mount type=bind,source=...`), so the old check (no argument starts with `/root/.claude`) passes again but no longer catches anything. The test now sets `CLAUDE_JSON_PATH` to a temp `.claude.json`. It checks that this fixture is the mounted file and that no argument contains `/root/.claude` anywhere.

`agentToolPolicyRuntime`: the generated MCP config starts the local adapter with the image's `/usr/local/bin/node`. The test already redirected the adapter script to the repo copy, but not that Node path. If `/usr/local/bin/node` is missing on the runner (my guess, since the nightly uses setup-node), the adapter never starts. When I pointed that path at a missing binary, I got exactly the nightly failure: the same 18-tool list and both failing tests. The test now uses the test's own Node (`process.execPath`), and the CLI's XDG config/cache dirs sit inside the temp HOME.

`followupHandoffReviewBlockers` (F23): as root, `chmod 000` can't produce EACCES. The test now injects EACCES, EPERM and EIO into the access check, so it runs under every uid. The real-`chmod` check is a separate test, skipped only when `process.getuid?.() === 0`, with that reason in the skip message. To prove it still catches problems, I temporarily made the code treat EACCES as "missing": the injected test failed even with the uid faked to 0.

`agentRunAdmission` (180 s timeout): the cause was a hang, not CPU. Two problems:
- The file never exited. Importing the Docker executor opens the database connection, and nothing closed it. GitHub-hosted runners don't have `redis-server`, so those tests are skipped there and the file finishes in about 1 s. The self-hosted host has Redis, so they run and the file hung until the 180 s limit. It now closes the connection in `after()`.
- The 12-process case failed intermittently (2 of 3 full runs here). The worker fixture exited 500 ms after finishing, sometimes before its Redis slot was released. It now waits until Redis no longer holds a slot for its containers (up to 30 s), and fails if the slot is never released.

Verification
- Normal user: all 4 files pass, with a locally built `redis-server` so the 25 Redis tests actually run. `agentRunAdmission` takes 70–83 s, including under a 2-CPU limit to mimic the runner's quota.
- Closest to root I could get: all 4 pass with `process.getuid()` faked to 0 (the F23 skip shows its reason) and a fake populated agent home: `.claude.json` with an extra MCP server, `.claude/settings.json`, `.credentials.json`, `.codex/config.toml`. The host-style variables (`CLAUDE_JSON_PATH`, `CLAUDE_CONFIG_DIR`, `CODEX_HOME`, `XDG_CONFIG_HOME`, `HOST_CLAUDE_DIR`) were set as well.
- Lint: `npm run lint -- --max-warnings 0` passes. Root `test/` isn't covered by ESLint or the typecheck gate, so I ran `tsc` on the 5 changed files: they have the same 4 type errors as before, and none are new.

A possible product issue, left unchanged
When the backend runs in a container and `CLAUDE_JSON_PATH` isn't set, the code falls back to `.claude.json` in the backend's own home. That's the same "uses the backend HOME" pattern the config-path code refuses for the credentials folder. The code comment says this is intended for dev/compose setups and it was only just reworked, so I didn't change it. It may be worth deciding separately whether it should also refuse.

Changed files: the 4 test files plus `test/fixtures/agent-admission/worker.ts`. There's nothing visual, so no previews.
@integry

integry commented Oct 29, 2026

Copy link
Copy Markdown
Owner

/review

@propr-dev

propr-dev Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor Author

✅ AI Code Review Complete requested by @integry

Posted 1 review:

View Task Details

@propr-dev

propr-dev Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

🔍 AI Code Review — codex:gpt-6-astra

Overall Evaluation

Ready to merge within the visible scope. The changes improve host isolation and cleanup without visibly weakening the existing assertions. Current head checks report 18 passed and no failures.

✅ Credential isolation — The temporary Claude JSON fixture explicitly verifies the selected bind source and strengthens the assertion against /root/.claude references.

✅ Permission coverage — Injected EACCES, EPERM and EIO failures preserve error-propagation coverage under root, while the real permission test remains enabled for non-root users.

✅ Cleanup synchronization — The worker waits for its own started containers’ admission records to disappear instead of relying on a fixed 500 ms delay.

This is a static review of the supplied code; no commands were run. The issue description is explicitly truncated, so acceptance criteria beyond the visible text could not be assessed.

Merge blockers

No merge blockers.

Suggestions

These are optional follow-ups and are not sent to /fix.

S1: 🟢 Record target-runner validation

A disposable root-environment run with synthetic agent configuration and the pinned CLIs would strengthen confidence in the intended nightly-runner fix. Record which runtime tests actually executed, since CLI version checks can skip them. This is supplemental validation, not a demonstrated defect in the changed code; the supplied passing checks do not establish those specific execution conditions.

Score

The changes are focused, preserve substantive assertions, and have passing current-head checks. Confidence is limited by static-only review and the unavailable remainder of the issue specification.

Score: 8/10


🤖 Review Details

  • Model: GPT-6 Astra
  • Time: 43s
  • Tokens: 43,630 (43,104 in / 526 out)
  • Cost: $0.35

View Task


💡 Next step: Comment /fix to address every F# merge blocker, /fix all for every pending blocker and suggestion, or name records explicitly, as in /fix F3 S5.
F# and S# IDs increment across review comments and remain permanent, so selectors such as /fix F3 F5 stay unambiguous across cycles. S# suggestions stay optional: they are implemented only when you name them or request /fix all, and they never relax a merge blocker.

Copy and edit to choose records from this review:

/fix S1

🤖 Review by ProPR

@integry

integry commented Oct 29, 2026

Copy link
Copy Markdown
Owner

Validated on the target condition: the merged result runs as root in node:24-bookworm with a populated fake /root/.claude and /root/.claude.json. agentDefaultConfigPaths, agentRunAdmission and agentToolPolicyRuntime now pass as root, and all four pass as uid 1000.

One more root-only failure is left in test/followupHandoffReviewBlockers.test.ts (62 of 64 pass as root):

✖ F23: recovery cleanup keeps the handoff when the worktree path is inaccessible
  AssertionError: an access error is not release evidence
  + 'settled'
  - 'cleanup_pending'
  at test/followupHandoffReviewBlockers.test.ts:1094

It's the same cause as the F23 case you already fixed: root bypasses the chmod-based 'inaccessible' fixture. Apply the same approach (an injected access error, or a uid-0-only skip with an explicit reason that still enforces it elsewhere), and check the whole file for any other chmod/EACCES fixtures. Rerun the file as root and as a normal user.

@propr-dev

propr-dev Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor Author

✅ Applied the requested follow-up changes in commit e06873b

Summary of Changes

I've fixed the last root-only failure in test/followupHandoffReviewBlockers.test.ts. I couldn't run it as real root, because this container has no Docker, sudo or user namespaces. I could only fake uid 0, so your node:24-bookworm root run is still the one that confirms it.

What changed (test/followupHandoffReviewBlockers.test.ts:1085): "F23: recovery cleanup keeps the handoff when the worktree path is inaccessible" no longer uses chmod 0o000. Instead, fs.promises.access throws an injected EACCES for any path under the fixture directory, and passes everything else through. This is the same approach as the earlier F23 git-ops test. The test now runs for every uid, with no skip.

I also detach the worktree's branch before cleanup, which makes the test stricter than before. With no worktree registered on the branch, reading the access error as "missing" would wrongly settle the handoff. With the old chmod fixture, git itself also failed, so the test passed whether or not that bug was there.

Rest of the file: the only other chmod fixture is "F23: a real permission-denied worktree path rejects…". It already skips under uid 0 with an explicit reason, and the injected EACCES/EPERM/EIO test above it covers the same rule for root. Nothing else in the file uses chmod or relies on permissions.

Test results:

  • Normal user (uid 1000): 64 of 64 pass.
  • process.getuid() faked to 0: 63 pass and 1 skipped (the real-chmod test, which shows its reason). The rewritten test doesn't depend on file permissions, so being root can't change its result.
  • Negative check: I temporarily changed src/jobs/followupHandoffGitOps.ts to treat EACCES as "missing". The rewritten test then failed with exactly your root output (+ 'settled' / - 'cleanup_pending'). I restored the file afterwards, so production code is unchanged.

Only the test file changed. There's nothing visual, so no previews.


🤖 Implementation Details

  • Model: Claude Opus 5.5 (medium)
  • Requested By: @integry
  • Time: 4m 24s
  • Tokens: 517,603 (512,461 in / 5,142 out)
  • Cost: $0.42

Undo Changes • View Task Execution


💡 ProPR Slash Commands
Command Description Example
/merge Merge target branch into this PR and resolve conflicts /merge
/review Request an AI code review /review or /review claude-sonnet
/fix Address review blockers, or blockers and suggestions with /fix all /fix, /fix all, or /fix F3 S5
/switch Change the AI model for this PR /switch claude-opus
/use Override the model for a single follow-up run /use claude-sonnet
/ultrafix Loop review→fix cycles until score goal is met /ultrafix or /ultrafix goal=8 max=10

🤖 Generated by ProPR
Processing comment ID: 6501900004✓

…offReviewBlockers.test.ts`. I couldn't run it as real root, because this container has no Docker, sudo or user namespaces. I could only fake uid 0, so your `node:24-bookworm` root run is still the one that confirms it.

I've fixed the last root-only failure in `test/followupHandoffReviewBlockers.test.ts`. I couldn't run it as real root, because this container has no Docker, sudo or user namespaces. I could only fake uid 0, so your `node:24-bookworm` root run is still the one that confirms it.

**What changed** (`test/followupHandoffReviewBlockers.test.ts:1085`): "F23: recovery cleanup keeps the handoff when the worktree path is inaccessible" no longer uses `chmod 0o000`. Instead, `fs.promises.access` throws an injected `EACCES` for any path under the fixture directory, and passes everything else through. This is the same approach as the earlier F23 git-ops test. The test now runs for every uid, with no skip.

I also detach the worktree's branch before cleanup, which makes the test stricter than before. With no worktree registered on the branch, reading the access error as "missing" would wrongly settle the handoff. With the old chmod fixture, git itself also failed, so the test passed whether or not that bug was there.

**Rest of the file:** the only other chmod fixture is "F23: a real permission-denied worktree path rejects…". It already skips under uid 0 with an explicit reason, and the injected `EACCES`/`EPERM`/`EIO` test above it covers the same rule for root. Nothing else in the file uses chmod or relies on permissions.

**Test results:**
- **Normal user (uid 1000):** 64 of 64 pass.
- **`process.getuid()` faked to 0:** 63 pass and 1 skipped (the real-chmod test, which shows its reason). The rewritten test doesn't depend on file permissions, so being root can't change its result.
- **Negative check:** I temporarily changed `src/jobs/followupHandoffGitOps.ts` to treat `EACCES` as "missing". The rewritten test then failed with exactly your root output (`+ 'settled'` / `- 'cleanup_pending'`). I restored the file afterwards, so production code is unchanged.

Only the test file changed. There's nothing visual, so no previews.

PR: #3383
Comment by: @integry (ID: 6501900004)
Model: Claude Opus 5.5
@integry

integry commented Oct 29, 2026

Copy link
Copy Markdown
Owner

/review

@propr-dev

propr-dev Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor Author

✅ AI Code Review Complete requested by @integry

Posted 1 review:

View Task Details

@propr-dev

propr-dev Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

🔍 AI Code Review — codex:gpt-6-astra

Overall Evaluation

The PR makes the tests less dependent on host credentials, executable locations, permissions, and cleanup timing without changing production code. The supplied changes appear merge-ready within scope, conditional on pending checks completing successfully.

✅ UID-independent error coverage — Injected access errors exercise the real handoff logic under root. Detaching the worktree ensures that incorrectly treating an access error as absence would fail the recovery assertion.

✅ Explicit fixture isolation — The Claude seed uses a temporary file, and runtime tests set temporary XDG directories while retaining tool-selection and credential-leak assertions.

✅ Observable cleanup completion — The admission worker waits for its started containers’ Redis reservations to disappear instead of relying on a fixed delay.

This was a static review; no commands were run. The supplied issue description is truncated, so requirements beyond its visible text could not be assessed.

Merge blockers

No merge blockers.

Suggestions

These are optional follow-ups and are not sent to /fix.

S2: 🟢 Record final UID validation

Attach results for the updated followupHandoffReviewBlockers.test.ts under root and an ordinary UID, as requested in the authorized follow-up. The historical root failure is addressed by the current diff, but the supplied evidence does not include those final reruns. This is a verification follow-up, not a demonstrated code defect.

Score

The changes preserve meaningful assertions and address the visible host-dependent failures. No code blocker was found; five current-head checks remain pending, and final root/non-root rerun results are not supplied.

Score: 8/10


🤖 Review Details

  • Model: GPT-6 Astra
  • Time: 39s
  • Tokens: 44,987 (44,483 in / 504 out)
  • Cost: $0.36

View Task


💡 Next step: Comment /fix to address every F# merge blocker, /fix all for every pending blocker and suggestion, or name records explicitly, as in /fix F3 S5.
F# and S# IDs increment across review comments and remain permanent, so selectors such as /fix F3 F5 stay unambiguous across cycles. S# suggestions stay optional: they are implemented only when you name them or request /fix all, and they never relax a merge blocker.

Copy and edit to choose records from this review:

/fix S2

🤖 Review by ProPR

@integry
integry merged commit d74e3f4 into main Oct 29, 2026
42 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make 4 test files robust on the self-hosted nightly runner, without weakening what they verify. Check current upstream main first.

1 participant