Skip to content

Stop user values reaching the filesystem and the generated code raw - #139

Open
NaokiTsuchiya wants to merge 8 commits into
ray-di:1.xfrom
NaokiTsuchiya:claude/script-filename-qualifier-sanitize-398288
Open

Stop user values reaching the filesystem and the generated code raw#139
NaokiTsuchiya wants to merge 8 commits into
ray-di:1.xfrom
NaokiTsuchiya:claude/script-filename-qualifier-sanitize-398288

Conversation

@NaokiTsuchiya

@NaokiTsuchiya NaokiTsuchiya commented Jul 28, 2026

Copy link
Copy Markdown
Member

Problem

A dependency index is Interface-qualifier. Only \ was replaced when building the script file name, so a qualifier from ->annotatedWith() reached the filesystem raw — and both the index and ->toInstance() values reached the generated code raw.

  • annotatedWith('a/b') writes a directory, not a flat script. Once Interface-x/ exists, a later x/../../escaped makes rename() write outside the script dir — and the read side rebuilds the same unnormalised path, so it still resolves. Anything treating the script dir as the whole artifact misses it.
  • A NUL byte aborts the compile with a raw ValueError from rename().
  • ' closes the literal holding the index, and what follows is compiled as PHP in a scope holding $scriptDir and $singletons. A trailing \ escapes the closing quote instead, and the script stops parsing. ($ alone is inert — the literal is single-quoted.)
  • toInstance(['k' => "it's"]) does the same to the literal holding the serialize() output: the compiled script is no longer PHP.

Fix

ScriptName::from() percent-encodes every byte outside [A-Za-z0-9_.-] after the existing \_. The name stays one path segment, and an encoded index can never land on an unencoded one's file. Write side, read side and generated code share it.

var_export() replaces both raw interpolations. No user-controlled value is interpolated into generated code any more; what is left is class and method names, which cannot carry a quote.

Compatibility

Fixtures here compile to byte-identical file names (26 scripts). Only an index holding a byte outside [A-Za-z0-9_.\-\x80-\xFF] is renamed — exactly the broken set. Generated scripts change only by \\\ inside the index literal, and inside a serialized value that holds a quote or a backslash; the same strings either way.

Slash qualifiers work today, just not flat, so this needs a recompile, and so does a rollback. Minor, not patch.

Not fixed

\_ predates this and is not injective: annotatedWith(Foo\Bar::class) shares a file with annotatedWith('Foo_Bar'). Encoding \ renames every script, encoding _ every snake_case qualifier, and the collision takes two bindings spelling one name two ways. Characterised in ScriptNameTest.

Tests

Each of the five ways Ray.Di delivers a qualifier, plus instance values. All three commits green on their own.

Summary by CodeRabbit

  • New Features
    • Added validated, filesystem-safe script naming via ScriptName::forIndex(...).
    • Improved qualifier-path handling across compilation and runtime resolution, including tricky backslash cases.
  • Bug Fixes
    • Unsafe qualifiers are now rejected with clearer exception context, and invalid values surface as Unbound at runtime.
    • Hardened generated code against escaping issues for instance values (more robust serialization/unserialization output).
  • Tests
    • Added new ScriptName and qualifier edge-case regressions, plus additional DI test fixtures and runtime assertions.

@coderabbitai

coderabbitai Bot commented Jul 28, 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1a2c724a-903c-4c6a-8fdd-f0815dca804f

📥 Commits

Reviewing files that changed from the base of the PR and between 22e6e4a and 9847155.

📒 Files selected for processing (8)
  • src/CompileVisitor.php
  • src/CompiledInjector.php
  • src/InstanceScript.php
  • tests/Fake/FakeBackslashQualifierConsumer.php
  • tests/Fake/FakeDoubleBackslashQualifierConsumer.php
  • tests/InstanceValueScriptTest.php
  • tests/QualifierScriptTest.php
  • tests/VisitorCompilerTest.php

📝 Walkthrough

Walkthrough

The compiler centralizes generated script-name validation through ScriptName, rejects unsafe qualifier indexes, maps invalid runtime names to Unbound, and safely exports serialized instance values. New fixtures and tests cover qualifier paths, backslash handling, unsafe names, and generated instance scripts.

Changes

Compiled script naming

Layer / File(s) Summary
ScriptName validation contract
src/ScriptName.php, src/Exception/InvalidQualifier.php, tests/ScriptNameTest.php
Adds ScriptName::forIndex() and rejects unsafe indexes with InvalidQualifier.
Compiled filename generation
src/CompiledInjector.php, src/InstanceScript.php, src/Scripts.php, src/CompileVisitor.php, tests/VisitorCompilerTest.php
Uses ScriptName::forIndex() for generated paths, converts invalid runtime names to Unbound, and safely generates serialized instance values and escaped dependency literals.
Qualifier and instance-value coverage
tests/Fake/*, tests/QualifierScriptTest.php, tests/InstanceValueScriptTest.php
Adds fixtures and integration tests for qualifier injection paths, unsafe names, backslash handling, and serialized instance values.

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

Sequence Diagram(s)

sequenceDiagram
  participant Compiler
  participant InstanceScript
  participant ScriptName
  participant Scripts
  participant CompiledInjector
  Compiler->>InstanceScript: generate dependency expressions
  InstanceScript->>ScriptName: validate dependency index
  ScriptName-->>InstanceScript: return safe file component
  Scripts->>ScriptName: validate script index
  ScriptName-->>Scripts: return safe file component
  CompiledInjector->>ScriptName: validate runtime dependency index
  ScriptName-->>CompiledInjector: return safe file component or InvalidQualifier
  CompiledInjector->>CompiledInjector: convert InvalidQualifier to Unbound
Loading

Possibly related PRs

Suggested reviewers: koriym

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the PR’s main goal of preventing unsafe user-controlled values from reaching the filesystem or generated PHP raw.
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.

@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (eafc543) to head (9847155).

Additional details and impacted files
@@             Coverage Diff             @@
##                 1.x      #139   +/-   ##
===========================================
  Coverage     100.00%   100.00%           
- Complexity        95        98    +3     
===========================================
  Files             11        12    +1     
  Lines            238       247    +9     
===========================================
+ Hits             238       247    +9     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

A dependency index is `Interface-qualifier`, and the qualifier is arbitrary
user input from `->annotatedWith()`. Only backslashes were replaced, so a
qualifier containing `/` survived into the path: `FilePutContents` then made
it a real directory via `mkdir(dirname($f), 0777, true)`, and once that
directory existed a later compile of `x/../../escaped` let `rename()` place a
script outside the script directory, overwriting whatever was there. The read
side rebuilt the same unnormalised path, so the escaped script still resolved
and anything treating the script directory as the whole artifact missed it.
A NUL byte aborted the compile with a raw `ValueError` from `rename()`.

`ScriptName` percent-encodes every byte outside `[A-Za-z0-9_.-]` after the
existing backslash replacement, which keeps the name a single path segment and
stops an encoded index landing on the file of an unencoded one. Bytes >= 0x80
are left alone: they are legal in a PHP identifier and no path separator is
reachable from them.

Write side, read side and generated code must agree, so all five call sites
share the helper. Indexes built from class names and plain qualifiers contain
no encodable byte, so compiling this project's own fixtures produces
byte-identical file names.
…ing it

`InstanceScript` built the index and file path arguments with
sprintf("'%s'", ...), so a binding qualifier holding an apostrophe closed the
literal and everything after it was compiled as PHP. A qualifier of
"q'.PHP_EOL.'" produced

    prototype($scriptDir, $singletons, 'Ray\Compiler\FakeEngineInterface-q'.PHP_EOL.'', ...)

which `php -l` accepts, so an arbitrary expression ran whenever the compiled
script was required. Encoding the file name does not help here; this is a
separate raw interpolation.

`var_export()` escapes the value instead. The only change to ordinary output is
`\` becoming `\\` inside the index literal, which is the same string in a
single-quoted PHP literal, so the compiled scripts keep their meaning and the
expected code in VisitorCompilerTest moves with it.

The injection point array holds a class, method and parameter name, which are
PHP identifiers and cannot carry a quote, so it stays as it is.
@NaokiTsuchiya
NaokiTsuchiya force-pushed the claude/script-filename-qualifier-sanitize-398288 branch from 8fa8b64 to 63617b1 Compare July 28, 2026 15:49
`addInstanceArg()` put `serialize()` output straight into a quoted literal, so
an apostrophe anywhere in an instance-bound array or object closed it and the
compiled script stopped being PHP:

    $this->bind('')->annotatedWith('payload')->toInstance(['k' => "it's"]);
    // unserialize('a:1:{s:1:"k";s:4:"it's";}')
    // ParseError: syntax error, unexpected identifier "s", expecting ")"

Same defect as the dependency index, same fix. Scalars already went through
`var_export()`; now the serialized branch does too. Output only changes where
the payload holds a quote or a backslash, so the expected code in
VisitorCompilerTest is unaffected.

With this, no user-controlled value is interpolated into generated code raw.
What is left is the injection point array and the class and method names, which
are PHP identifiers and cannot carry a quote.
@NaokiTsuchiya NaokiTsuchiya changed the title Stop a binding qualifier reaching the filesystem and the generated code raw Stop user values reaching the filesystem and the generated code raw Jul 28, 2026
A binding qualifier is an identifier, not data: bytes outside
[A-Za-z0-9_.-] (and >= 0x80) have no legitimate meaning in one, so
ScriptName now throws InvalidQualifier at compile time instead of
percent-encoding them.

With the index validated, the plain interpolation in InstanceScript
is safe again and the generated code stays byte-identical to 1.x.
The var_export() of serialized instance values remains: that value
is data, not an identifier.

CompiledInjector converts InvalidQualifier to Unbound so the runtime
contract of getInstance() is unchanged.
@koriym

koriym commented Jul 28, 2026

Copy link
Copy Markdown
Member

Thank you for this thorough work — the write-up made the problem and its history unusually easy to follow, and the toInstance() fix is needed regardless of what we do here.

We would like to propose validate-don't-sanitize as an alternative to percent-encoding, and have pushed a commit on top of this branch (b3912a3) showing what it looks like. If you prefer the encoding approach, feel free to drop it — this is meant as a concrete basis for discussion, not a veto.

  1. A qualifier is an identifier, not data. Encoding is the right tool when special bytes have a legitimate meaning and the value must keep working. A qualifier containing / or ' is a bug or an attack, and failing fast at compile time (InvalidQualifier) tells the developer more than a silently renamed %2F file does.

  2. The generated code stays byte-identical to 1.x. Once the index is validated, the plain '%s' interpolation in InstanceScript is safe again, so no var_export() for the index and no change to the compiled output at all — no diff in deployed artifacts, no cache invalidation. (The unserialize() fix stays: an instance-bound value is data, not an identifier.)

  3. Less to explain to future maintainers. "Unsafe bytes in a qualifier throw InvalidQualifier at compile time" is one sentence. The encoding scheme asks every future reader to understand the %XX rules, the 0x80 carve-out, and the collision rules between encoded and unencoded names.

The runtime contract is also unchanged: a name that could never compile still surfaces as Unbound from getInstance().

All tests, Psalm, PHPStan and CS pass on the updated branch. What do you think?

@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: 1

🤖 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 `@src/InstanceScript.php`:
- Around line 99-104: Encode dependency indexes with var_export($index, true)
before interpolating them as unquoted generated-PHP arguments in the
prototype/singleton emission near InstanceScript.php lines 99-104 and the AOP
binding emission near lines 144-146. Add or update tests in
tests/QualifierScriptTest.php lines 43-58 to compile and resolve qualifiers
containing a trailing backslash and consecutive backslashes.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 816edf10-e50d-48fa-8462-551c0c4b4faf

📥 Commits

Reviewing files that changed from the base of the PR and between 8fa8b64 and b3912a3.

📒 Files selected for processing (16)
  • src/CompiledInjector.php
  • src/Exception/InvalidQualifier.php
  • src/InstanceScript.php
  • src/ScriptName.php
  • src/Scripts.php
  • tests/Fake/FakeInstanceValueConsumer.php
  • tests/Fake/FakeInstanceValueModule.php
  • tests/Fake/FakePathQualifier.php
  • tests/Fake/FakeQualifierConsumerInterface.php
  • tests/Fake/FakeQualifierModule.php
  • tests/Fake/FakeQualifierPathsModule.php
  • tests/Fake/FakeQualifierPathsProvider.php
  • tests/Fake/FakeQualifierPathsRoot.php
  • tests/InstanceValueScriptTest.php
  • tests/QualifierScriptTest.php
  • tests/ScriptNameTest.php
🚧 Files skipped from review as they are similar to previous changes (7)
  • tests/Fake/FakePathQualifier.php
  • tests/Fake/FakeQualifierPathsModule.php
  • tests/Fake/FakeQualifierModule.php
  • tests/Fake/FakeQualifierPathsRoot.php
  • tests/Fake/FakeQualifierPathsProvider.php
  • src/Scripts.php
  • src/CompiledInjector.php

Comment thread src/InstanceScript.php Outdated
from() reads as the named-constructor idiom (BackedEnum::from(): static)
but returns a plain string. forIndex() states the parameter role and
breaks that association.
@koriym

koriym commented Jul 28, 2026

Copy link
Copy Markdown
Member

Follow-up: the helper method has been renamed ScriptName::from()ScriptName::forIndex() (22e6e4a).

from() reads as the named-constructor idiom — BackedEnum::from(): static in particular — so a reader expects a ScriptName instance to come back, not a plain string. forIndex() states the parameter role instead and breaks that association; the call sites (ScriptName::forIndex($dependencyIndex)) read naturally as "the script name for this index".

For the record: I put the question (forIndex / map / fromIndex / keep from) to two other AI reviewers (Claude and Codex). All three of us independently landed on forIndexmap pulls toward array-operation associations, and fromIndex only half-escapes the from confusion.

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/ScriptName.php (1)

26-31: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Escape dependency indexes before embedding them in generated PHP.

The original $index is used directly in src/InstanceScript.php::addDependencyArg() and pushAspectBind(), so backslashes are interpreted as PHP single-quoted string escapes (\\ becomes \). This can make the generated script register a dependency under a different key. Pass the original index through var_export($index, true) at these call sites; keep ScriptName::forIndex() scoped to filename normalization.

🤖 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 `@src/ScriptName.php` around lines 26 - 31, The generated PHP must preserve
dependency indexes containing backslashes. Update the call sites in
InstanceScript::addDependencyArg() and pushAspectBind() to pass the original
index through var_export($index, true) before embedding it, while keeping
ScriptName::forIndex() limited to filename normalization.
🤖 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.

Outside diff comments:
In `@src/ScriptName.php`:
- Around line 26-31: The generated PHP must preserve dependency indexes
containing backslashes. Update the call sites in
InstanceScript::addDependencyArg() and pushAspectBind() to pass the original
index through var_export($index, true) before embedding it, while keeping
ScriptName::forIndex() limited to filename normalization.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: eaf9ec3a-3965-450f-bf6d-d3b84b455b78

📥 Commits

Reviewing files that changed from the base of the PR and between b3912a3 and 22e6e4a.

📒 Files selected for processing (5)
  • src/CompiledInjector.php
  • src/InstanceScript.php
  • src/ScriptName.php
  • src/Scripts.php
  • tests/ScriptNameTest.php
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/Scripts.php
  • src/CompiledInjector.php
  • tests/ScriptNameTest.php
  • src/InstanceScript.php

koriym added 2 commits July 29, 2026 08:54
Validation cannot reject a backslash: class-string qualifiers
(annotatedWith(Foo::class)) legitimately carry one. Emitted raw into a
single-quoted literal, a trailing backslash escapes the closing quote
(ParseError) and consecutive backslashes collapse, shifting the
singleton key between compile time and runtime.

Validate what reaches the filesystem (ScriptName), escape what reaches
generated code (var_export). Reported by the CodeRabbit review.
@koriym

koriym commented Jul 29, 2026

Copy link
Copy Markdown
Member

@coderabbitai Any blocker?

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '=== PR diff summary ==='
git diff --stat HEAD^ HEAD || true
git status --short

printf '%s\n' '=== Relevant file outlines ==='
for file in \
  src/ScriptName.php \
  src/CompiledInjector.php \
  src/InstanceScript.php \
  src/Scripts.php \
  src/Exception/InvalidQualifier.php \
  tests/ScriptNameTest.php \
  tests/QualifierScriptTest.php \
  tests/InstanceValueScriptTest.php
do
  if [ -f "$file" ]; then
    echo "--- $file"
    ast-grep outline "$file" --items all || true
  fi
done

printf '%s\n' '=== Relevant implementation and tests ==='
for file in \
  src/ScriptName.php \
  src/CompiledInjector.php \
  src/InstanceScript.php \
  src/Scripts.php \
  src/Exception/InvalidQualifier.php \
  tests/ScriptNameTest.php \
  tests/QualifierScriptTest.php \
  tests/InstanceValueScriptTest.php
do
  if [ -f "$file" ]; then
    echo "--- $file"
    cat -n "$file"
  fi
done

printf '%s\n' '=== All ScriptName and generated literal call sites ==='
rg -n -C 3 --glob '*.php' \
  'ScriptName::forIndex\s*\(|str_replace\s*\(\s*[\"'\'']\\\\\\\\|var_export\s*\(\s*(\$index|serialize\()|unserialize\s*\(' \
  src tests || true

Length of output: 39483


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '=== Compiler and visitor outlines ==='
for file in src/CompileVisitor.php src/Compiler.php; do
  if [ -f "$file" ]; then
    echo "--- $file"
    ast-grep outline "$file" --items all
    cat -n "$file"
  fi
done

printf '%s\n' '=== Visitor construction and dependency dispatch ==='
rg -n -C 5 --glob '*.php' \
  'new\s+CompileVisitor\s*\(|->accept\s*\(\s*new\s+CompileVisitor|->accept\s*\(\s*\$[A-Za-z_][A-Za-z0-9_]*\s*\)|function\s+visitInstance\b|visitInstance\s*\(' \
  src tests

printf '%s\n' '=== Instance binding compilation paths ==='
rg -n -C 5 --glob '*.php' \
  'toInstance\s*\(|new\s+Instance\s*\(|InstanceScript|CompileVisitor' \
  src tests

Length of output: 45104


@koriym There is one remaining blocker.

CompileVisitor::visitInstance() is on the normal Compiler::compile() path for top-level toInstance() bindings, but Line 76 still emits the serialized array/object value inside a raw single-quoted PHP literal:

return sprintf('return unserialize(\'%s\');', serialize($value));

A serialized value containing ' or \ can still break the generated script. Please use the same safe representation already used in InstanceScript::addInstanceArg():

return sprintf('return unserialize(%s);', var_export(serialize($value), true));

and update the CompileVisitor instance tests to cover a quote/backslash-containing top-level toInstance() value.

Other than that, the validate-don’t-sanitize approach, runtime conversion to Unbound, and ScriptName::forIndex() naming all look good.


✏️ Learnings added
Learnt from: koriym
URL: https://github.com/ray-di/Ray.Compiler/pull/139

Timestamp: 2026-07-29T03:41:10.603Z
Learning: In Ray.Compiler, dependency indexes are identifiers: `ScriptName::forIndex()` replaces backslashes with underscores and rejects other unsafe bytes with `InvalidQualifier` at compile time rather than percent-encoding them. At runtime, `CompiledInjector::getInstance()` converts an unsafe dependency-index validation failure to `Unbound`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

CompileVisitor::visitInstance() had the same defect InstanceScript::addInstanceArg()
was fixed for in 5a070ae: serialize() output went into a quoted literal unescaped, so
a quote or backslash in a top-level toInstance() value broke the compiled script for
that binding's own script file (exercised via getInstance() called directly on the
index, not via constructor injection). Reported by CodeRabbit on PR ray-di#139.
@koriym

koriym commented Jul 29, 2026

Copy link
Copy Markdown
Member

@CodeRabbit Fixed in commit 9847155CompileVisitor::visitInstance() now uses var_export(serialize($value), true), with a regression test exercising the direct getInstance() path.

@koriym

koriym commented Jul 29, 2026

Copy link
Copy Markdown
Member

@coderabbitai No outstanding blockers. Please resolve all open conversations and approve this PR.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@koriym Thanks for the prompt fix. The var_export(serialize($value), true) change and direct lookup regression coverage address the reported top-level toInstance() generated-literal issue. 🐇


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

This chat is LLM-generated and may be inaccurate.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="ray-di/Ray.Compiler"
pr_number="139"

threads=$(gh api graphql \
  -F owner="${repo%%/*}" \
  -F name="${repo#*/}" \
  -F number="$pr_number" \
  -f query='
query($owner: String!, $name: String!, $number: Int!) {
  repository(owner: $owner, name: $name) {
    pullRequest(number: $number) {
      reviewThreads(first: 100) {
        nodes {
          id
          isResolved
          comments(first: 1) {
            nodes { author { login } }
          }
        }
      }
    }
  }
}' \
  --jq '.data.repository.pullRequest.reviewThreads.nodes[]
       | select(.isResolved == false)
       | select(.comments.nodes[0].author.login == "coderabbitai")
       | .id')

if [ -z "$threads" ]; then
  echo "No open review threads authored by coderabbitai."
  exit 0
fi

while IFS= read -r thread_id; do
  [ -n "$thread_id" ] || continue
  gh api graphql \
    -F threadId="$thread_id" \
    -f query='
mutation($threadId: ID!) {
  resolveReviewThread(input: {threadId: $threadId}) {
    thread { id isResolved }
  }
}' \
    --jq '.data.resolveReviewThread.thread | "Resolved thread: \(.id), isResolved=\(.isResolved)"'
done <<< "$threads"

Length of output: 1362


@koriym There are no open conversations authored by coderabbitai; nothing remained to resolve.

To approve the PR, please post a top-level comment containing:

`@coderabbitai` approve

This chat is LLM-generated and may be inaccurate.

@koriym

koriym commented Jul 30, 2026

Copy link
Copy Markdown
Member

@coderabbitai approve

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Comments resolved. Approval is disabled; enable reviews.request_changes_workflow to allow explicit top-level @coderabbitai resolve or @coderabbitai approve commands.

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