Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/chainguard/self.check-pull-request-size.sts.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
issuer: https://token.actions.githubusercontent.com

subject: repo:DataDog/dd-trace-java:pull_request

claim_pattern:
event_name: pull_request
job_workflow_ref: DataDog/dd-trace-java/\.github/workflows/check-pull-request-size\.yaml@refs/(pull/[0-9]+/merge|heads/.+)

permissions:
issues: write
pull_requests: write
27 changes: 27 additions & 0 deletions .github/scripts/count-added-lines.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
# Counts the lines added from the merge base of two revisions, excluding moved lines and lock files.
# Usage: count-added-lines.sh <base> [head]
#
# Moved lines (blocks of at least 20 alphanumeric characters deleted and added elsewhere, even
# re-indented or in another file) are told apart from added lines using diff colors:
# added lines are painted green and moved lines blue, then only the green lines are counted.
set -euo pipefail

BASE=${1:?Usage: $0 <base> [head]}
HEAD=${2:-HEAD}

git \
-c color.diff.meta=bold \
-c color.diff.new=green \
-c color.diff.newMoved=blue \
diff \
--color=always \
--color-moved=blocks \
--color-moved-ws=allow-indentation-change \
--ws-error-highlight=none \
--merge-base \
--find-renames \
--no-ext-diff \
"$BASE" "$HEAD" \
-- . ':(exclude,glob)**/gradle.lockfile' |
awk '/^\033\[32m\+/ { count++ } END { print count + 0 }'
9 changes: 9 additions & 0 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ _Actions:_

_Recovery:_ Update the pull request or add a comment to trigger the action again.

### check-pull-request-size [🔗](check-pull-request-size.yaml)

_Trigger:_ When creating or updating a pull request targeting `master` or a patch release (`release/vM.N.x`) branch, or when labels are updated. Draft pull requests are skipped.

_Action:_ Fail the pull request if it adds 1000 lines or more, then comment asking to split it into smaller stacked pull requests.
Deleted lines, moved code (detected using `git diff --color-moved`), and `gradle.lockfile` files are not counted.

_Recovery:_ Split the pull request into smaller stacked pull requests. To override this check entirely, add the `tag: override pr size check` label to the PR. Remove the label to re-enable the check.

### enforce-datadog-merge-queue [🔗](enforce-datadog-merge-queue.yaml)

_Trigger:_ When creating or updating a pull request, or when a pull request is added to GitHub merge queue.
Expand Down
110 changes: 110 additions & 0 deletions .github/workflows/check-pull-request-size.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
name: Check pull request size
on:
pull_request:
types: [opened, reopened, edited, ready_for_review, labeled, unlabeled, synchronize]
branches:
- master
- "release/v*"

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
check_pull_request_size:
name: Check pull request size
# Skip draft pull requests
if: ${{ !github.event.pull_request.draft }}
permissions:
contents: read # Required to check out the pull request changes
id-token: write # Required for OIDC token federation
runs-on: ubuntu-latest
steps:
- name: Checkout pull request changes
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # 7.0.1
with:
fetch-depth: 2 # Fetch the pull request merge commit and its parents
filter: blob:none
sparse-checkout: .github/scripts
Comment on lines +27 to +28

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 Preserve the requested sparse checkout

On every workflow run, filter: blob:none causes this pinned version of actions/checkout to ignore the following sparse-checkout input—the action explicitly documents that filter overrides sparse checkout. Consequently the checkout materializes the repository's entire roughly 12,000-file, 111 MiB snapshot instead of only .github/scripts, adding avoidable I/O to a check triggered for every PR update and label change. Remove the conflicting filter or configure partial and sparse checkout manually so the intended path restriction is honored.

Useful? React with 👍 / 👎.

persist-credentials: false
- name: Count added lines
id: count-added-lines
run: |
# The first parent of the pull request merge commit is the base branch
ADDED_LINES=$(.github/scripts/count-added-lines.sh HEAD^1 HEAD)

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 Run the counter from a trusted revision

On any PR that modifies .github/scripts/count-added-lines.sh, the default pull_request checkout supplies the PR merge commit, so this line executes attacker-controlled shell code while the job has id-token: write. GitHub exposes OIDC request credentials to jobs with that permission (OIDC reference); the script can therefore exfiltrate a JWT accepted by the new STS policy and obtain its issues: write/pull_requests: write token. Even without stealing credentials, replacing the script with echo 0 bypasses the size limit. Run a counter sourced from the trusted base revision, ideally before or separately from the OIDC-enabled job.

AGENTS.md reference: AGENTS.md:L74-L74

Useful? React with 👍 / 👎.

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.

P1 Keep PR-controlled execution outside the OIDC trust boundary

A PR modifying count-added-lines.sh executes author-controlled shell code in a job with id-token: write. That code can request an OIDC token and exchange it under the new policy, which accepts PR merge refs and grants repository-wide issue and pull-request write permissions. Disabling persisted checkout credentials does not restrict OIDC access. Separate untrusted counting from credentialed commenting and bind token issuance to a trusted workflow revision.

React: 👍 useful · 👎 wrong · 😕 unclear · 👀 right, but not acting on it

What each reaction means
  • 👍 Useful: correct, and you're fixing it or will.
  • 👎 Wrong: the finding is incorrect, for example the bug can't happen or the code was misread.
  • 😕 Unclear: you can't tell what it means or why it matters.
  • 👀 Right, not acting: correct, but intended, out of scope for this PR, a known risk, or too minor.

🤖 Bits Code Review · Open Bits AI session

echo "Added lines: $ADDED_LINES"
echo "added_lines=$ADDED_LINES" >> "$GITHUB_OUTPUT"
- name: Get GitHub Token via dd-octo-sts
id: generate-token
uses: DataDog/dd-octo-sts-action@c85802a58e202b7e764b5441e4378c76043c8108 # v1.0.5
with:
scope: DataDog/dd-trace-java
policy: self.check-pull-request-size
- name: Check pull request size
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # 9.0.0
env:
ADDED_LINES: ${{ steps.count-added-lines.outputs.added_lines }}
MAX_ADDED_LINES: 1000
with:
github-token: ${{ steps.generate-token.outputs.token }}
script: |
const managedMarker = '<!-- dd-trace-java-check-pull-request-size -->'
const overrideLabel = 'tag: override pr size check'
const addedLines = parseInt(process.env.ADDED_LINES, 10)
const maxAddedLines = parseInt(process.env.MAX_ADDED_LINES, 10)

// Look for the previous managed comment
const comments = await github.paginate(github.rest.issues.listComments, {
issue_number: context.payload.pull_request.number,
owner: context.repo.owner,
repo: context.repo.repo
})
const existingComment = comments.find(comment => comment.body?.includes(managedMarker))

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.

P2 Verify comment ownership before updating or deleting it

If a contributor quotes a warning containing the managed marker, this lookup can select their comment instead of the bot's. Subsequent runs target it for overwriting when oversized or deletion when below the limit or overridden, leaving the actual warning untouched. Focused mocked execution confirmed all three selection paths. Require the expected issuing app's identity together with the marker before modifying a comment.

React: 👍 useful · 👎 wrong · 😕 unclear · 👀 right, but not acting on it

What each reaction means
  • 👍 Useful: correct, and you're fixing it or will.
  • 👎 Wrong: the finding is incorrect, for example the bug can't happen or the code was misread.
  • 😕 Unclear: you can't tell what it means or why it matters.
  • 👀 Right, not acting: correct, but intended, out of scope for this PR, a known risk, or too minor.

🤖 Bits Code Review · Open Bits AI session


const deleteManagedComment = async () => {
if (existingComment) {
await github.rest.issues.deleteComment({
comment_id: existingComment.id,
owner: context.repo.owner,
repo: context.repo.repo
})
}
}

// Check for override label — skip the check if label present
const labels = context.payload.pull_request.labels.map(l => l.name)
if (labels.includes(overrideLabel)) {
await deleteManagedComment()
console.log(`${overrideLabel} label detected — skipping the check.`)
return
}

if (addedLines < maxAddedLines) {
await deleteManagedComment()
console.log(`Pull request adds ${addedLines} lines, below the ${maxAddedLines} lines limit.`)
return
}

const body = `**❌ Pull Request Too Large**\n\n` +
`This pull request adds **${addedLines}** lines while the limit is **${maxAddedLines}** lines.\n` +
`Deleted lines, moved code, and \`gradle.lockfile\` files are not counted.\n\n` +
`Please split it into smaller stacked pull requests, each one targeting the branch of the previous one. ` +
`See the [pull request size guidelines](https://github.com/DataDog/dd-trace-java/blob/master/CONTRIBUTING.md#pull-request-size) for more details.\n\n` +
`If this PR cannot be split, exceptionnaly add the \`${overrideLabel}\` label to bypass this workflow.\n\n` +
managedMarker
if (existingComment) {
await github.rest.issues.updateComment({
comment_id: existingComment.id,
owner: context.repo.owner,
repo: context.repo.repo,
body
})
} else {
await github.rest.issues.createComment({
issue_number: context.payload.pull_request.number,
owner: context.repo.owner,
repo: context.repo.repo,
body
})
}
core.setFailed(`Pull request adds ${addedLines} lines, above the ${maxAddedLines} lines limit. See PR comment for details. To bypass this workflow, add the '${overrideLabel}' label.`)
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ docs/ Developer documentation (see below)
## PR conventions

- Title: imperative verb sentence describing user-visible change (e.g. "Fix span sampling rule parsing")
- Size: keep PRs small (under 1000 added lines), split larger changes into stacked PRs.

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.

note: I suggest to explicitly write that agents are not allowed to add this tag. I.e. Only human judgment should be involved in this tag!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Do you think so? I would be fine having a bot adding the label on mechanical changes for example.
It's still up to the reviewer to asses the labels, like the groovy enforcement override.
Having the label added by a bot or a human without rationale help justifying a request for change review.

- Labels: always add `tag: ai generated` and at least one `comp:` or `inst:` label + one `type:` label
- Use `tag: no release notes` for internal/refactoring changes
- Open as draft first, convert to ready when reviewable
Expand Down
12 changes: 12 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,17 @@ and include them in the same PR.
This applies to changes in build scripts and `gradle/libs.versions.toml`.
Follow the [lockfile update workflow](docs/how_to_work_with_gradle.md#updating-lock-files) before requesting review.

### Pull request size

Pull requests should add less than 1000 lines to remain reviewable.
Deleted lines, moved code, and `gradle.lockfile` files are not counted.
Run `.github/scripts/count-added-lines.sh origin/master HEAD` to check the added line count of your branch.

Split larger changes into stacked pull requests: each pull request targets the branch of the previous one, and they are reviewed and merged in order.

>[!NOTE]
> If a pull request cannot be split (e.g. a large test migration), add the [`tag: override pr size check` label](#labels) to bypass the check.

### Title format

Pull request titles should briefly describe the proposed changes in a way that makes sense for the users.
Expand Down Expand Up @@ -140,6 +151,7 @@ Labels are used to not only categorize but also alter the continuous integration
* Refactoring pull requests
* CI and build tools improvements
* Minor changes like typo
* `tag: override pr size check` to bypass [the pull request size check](#pull-request-size) when a pull request cannot be split
* [The `run-tests:` category](https://github.com/DataDog/dd-trace-java/labels?q=run-tests%3A) to run continuous integration tests on a specific JVM in case of JVM specific changes
* `run-tests: flaky` to run the flaky tests on continuous integration as they are disabled by default

Expand Down
Loading