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
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,21 @@ Generation is identical to direct push. Only delivery changes: the same commit i

With the default `github.token`, the repository or organization must allow GitHub Actions to create pull requests. A GitHub App token or PAT can instead be passed as `github_token`. The same input is used for review comments and sync delivery.

### Keep the diagram on its own branch

Set `sync_strategy: branch` to keep the analysis off your code branches entirely:

```yaml
- uses: CodeBoarding/CodeBoarding-action@v1
with:
mode: sync
llm: hosted
target_branch: main

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

target_branch, is this the target in tehms of where the sync will happen or in terms of which branch we will sync with, unsure that wording is clear again.

i think that most of these things will be read by ppl or even more by their agents so proly descriptive and somewhat clear names are worth investing in.

sync_strategy: branch
```

Each sync adds one commit to `codeboarding/baseline` (set `baseline_branch` to change the name), an orphan branch that shares no history with `main`. It holds the same `.codeboarding/` files sync would otherwise commit to `main`, plus `.codeboarding/source.json` naming the commit they describe; the commit message carries the same sha as a `CodeBoarding-Source:` trailer. Pushes only ever fast-forward, `main` is never written, and no pull request is opened. Reviews read their base from the branch, and the web platform reads the latest diagram from it. The [baseline branch section](docs/COMMIT_STRATEGY.md#the-baseline-branch) covers what happens if the branch is deleted and a ruleset you can import to protect it.

## Inputs

| Input | Mode | Default | Description |
Expand All @@ -316,7 +331,8 @@ With the default `github.token`, the repository or organization must allow GitHu
| `parsing_model` | both | empty | Parsing-only override for `model`. |
| `depth_cap` | both | `2` | Positive integer maximum analysis depth, including full-analysis fallbacks. Changing it rebuilds incompatible state. |
| `github_token` | both | `${{ github.token }}` | Token for comments and sync delivery. |
| `sync_strategy` | sync | `push` | `push` or `pull_request`. |
| `sync_strategy` | sync | `push` | `push`, `pull_request`, or `branch`. |
| `baseline_branch` | both | `codeboarding/baseline` | Branch `sync_strategy: branch` writes; reviews read their base from it when it exists. |
| `target_branch` | sync | event branch | Branch receiving the baseline or rolling PR. |
| `force_full` | sync | `false` | Ignore the committed baseline for this run. |
| `warmstart_retention_days` | review | `1` | Days to keep the reusable analysis. Only the next run reads it. |
Expand Down
12 changes: 11 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,13 @@ inputs:
required: false
default: ${{ github.token }}
sync_strategy:
description: 'Sync delivery method: push or pull_request.'
description: 'Sync delivery method: push, pull_request, or branch (an orphan branch of its own, see baseline_branch).'
required: false
default: 'push'
baseline_branch:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

maybe the word baseline is not very intuitive for our users as we call this syncing, so I suppose maybe we should name it syncing_branch

description: 'Branch that sync_strategy branch keeps the analysis on. Reviews read it too.'
required: false
default: 'codeboarding/baseline'
target_branch:
description: 'Branch updated by sync mode. Defaults to the event branch.'
required: false
Expand Down Expand Up @@ -223,6 +227,7 @@ runs:
HEAD_AUTHOR_EMAIL: ${{ github.event.head_commit.author.email }}
TARGET_BRANCH_INPUT: ${{ inputs.target_branch }}
SYNC_STRATEGY: ${{ inputs.sync_strategy }}
BASELINE_BRANCH: ${{ inputs.baseline_branch }}
COMMENT_BODY: ${{ github.event.comment.body }}
AUTHOR_ASSOCIATION: ${{ github.event.comment.author_association }}
ISSUE_PR_URL: ${{ github.event.issue.pull_request.url }}
Expand Down Expand Up @@ -451,6 +456,8 @@ runs:
CHECKOUT_DIR: ${{ github.workspace }}/.codeboarding-target
STAGE_DIR: ${{ runner.temp }}/cb-state/${{ github.action }}/out
FORCE_FULL: ${{ inputs.force_full }}
SYNC_STRATEGY: ${{ inputs.sync_strategy }}
BASELINE_BRANCH: ${{ inputs.baseline_branch }}
CFG_HASH: ${{ steps.state.outputs.cfg_hash }}
# Lets a branch without a usable committed baseline catch up from a saved analysis.
ANCESTOR_LOOKUP: ${{ github.server_url == 'https://github.com' && steps.state.outputs.cfg_hash != '' }}
Expand All @@ -475,6 +482,8 @@ runs:
TARGET_BRANCH: ${{ steps.guard.outputs.target_branch }}
SYNC_BRANCH_START_SHA: ${{ steps.guard.outputs.sync_branch_start_sha }}
SYNC_STRATEGY: ${{ inputs.sync_strategy }}
BASELINE_BRANCH: ${{ inputs.baseline_branch }}
ENGINE_VERSION: ${{ steps.state.outputs.engine_version }}
GITHUB_TOKEN: ${{ inputs.github_token }}
GH_TOKEN: ${{ inputs.github_token }}
GH_ENTERPRISE_TOKEN: ${{ inputs.github_token }}
Expand Down Expand Up @@ -550,6 +559,7 @@ runs:
ANCESTOR_LOOKUP: ${{ github.server_url == 'https://github.com' && steps.state.outputs.cfg_hash != '' }}
# For rewriting the progress comment while a base is built from scratch.
PROGRESS_HEADER: ${{ steps.guard.outputs.comment_id }}
BASELINE_BRANCH: ${{ inputs.baseline_branch }}
BASE_REF: ${{ steps.guard.outputs.base_ref }}
REPOSITORY: ${{ github.repository }}
GH_HOST: ${{ github.server_url }}
Expand Down
47 changes: 47 additions & 0 deletions docs/COMMIT_STRATEGY.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ them:
|---|---|
| the published `codeboarding-base-<cfg>-<merge_base>` artifact with a compatible depth cap | none |
| no usable artifact — check out the merge base, seed from a compatible baseline committed there, catch up | one incremental, full if Core requires it |
| the baseline branch (`sync_strategy: branch`): its commit for the merge base, else for the nearest of the merge base's last 100 first-parent ancestors | none for the merge base itself (`saved`), one incremental otherwise (`ancestor`) |
| no compatible committed baseline either: the nearest `codeboarding-base-<cfg>-<sha>` artifact among the merge base's last 100 first-parent ancestors | one incremental from that commit to the merge base |
| none within 100 commits either | full analysis directly, at the configured `depth_cap` |

Expand Down Expand Up @@ -146,6 +147,52 @@ diffs against, recorded as a digest in `origin.json`. Two runs of the engine ove
one commit need not name components identically, so a head descended from one
base and a diagram drawn against another would report changes nobody made.

## The baseline branch

`sync_strategy: branch` keeps the analysis on a branch of its own,
`codeboarding/baseline` unless `baseline_branch` names another.

**What lives where.** The branch is an orphan: it shares no history with the code.
Each sync adds one commit holding the same `.codeboarding/` files the `push`
strategy would commit to the target branch, plus `.codeboarding/source.json`:

```json
{"schema": 1, "source_branch": "main", "source_sha": "<sha analysed>", "generated_at": "<iso>", "engine_version": "<v>"}
```

The commit is `chore(codeboarding): diagram of main @<sha7>` with a
`CodeBoarding-Source: <sha>` trailer. Engine output is never edited; which commit
it describes lives only in `source.json` and the trailer. The target branch is
never written, not even `.gitattributes`. The base artifacts are still published,
named for the analysed commit.

**How a sync writes it.** It seeds from the branch tip and runs incrementally. The
push is a fast-forward onto the tip it read, never forced. If the target branch
moved during the analysis, the result is dropped, as with `push`. If another sync
moved the baseline branch, it builds on that tip once. A push the remote refuses
while the tip did not move is a branch rule, and the run fails saying so.

**How a review reads it.** After an exact artifact and a baseline committed at the
merge base, a review lists the newest 100 commits of the branch (fetched without
file contents, so the listing costs commit messages only) and matches their
trailers against the merge base's first-parent history, up to 100 commits deep.
An entry for the merge base itself is `base_source=saved`; an entry for an
ancestor is caught up incrementally and reported as `ancestor`, with
`base_from_sha` naming it. Only then does it look for ancestor artifacts.

**If the branch is deleted**, the next sync creates it again as a new orphan,
seeding from a saved ancestor artifact when there is one and analyzing in full
otherwise. The history is lost; the current diagram is not.

**Protecting it.** Import [`baseline-branch-ruleset.json`](baseline-branch-ruleset.json)
under Settings, Rules, Rulesets, New ruleset, Import a ruleset. It blocks deleting
and force-pushing `codeboarding/baseline`, and lists the CodeBoarding Review app
(id `4021464`) as a bypass actor. Sync only fast-forwards, so these two rules
never stop it; the bypass keeps it working if you add stricter rules to that
branch later. If sync pushes with another app or a PAT, put that actor there
instead. Rulesets on a private repository need a paid GitHub plan (Pro, Team or
Enterprise); on Free they apply to public repositories only.

## Trust boundary

`static_analysis.pkl` is a Python pickle, so state derived from code the
Expand Down
22 changes: 22 additions & 0 deletions docs/baseline-branch-ruleset.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "CodeBoarding baseline branch",
"target": "branch",
"enforcement": "active",
"conditions": {
"ref_name": {
"include": ["refs/heads/codeboarding/baseline"],
"exclude": []
}
},
"rules": [
{ "type": "deletion" },
{ "type": "non_fast_forward" }
Comment on lines +11 to +13

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 baseline writes to the bypass actor

In repositories where contributors may push branches but the main branch requires reviewed PRs, this ruleset still lets any contributor create or fast-forward codeboarding/baseline; these rules block only deletion and non-fast-forward updates. Review and sync runs later pass that branch's .codeboarding/static_analysis.pkl through restore_baseline to the engine, which loads pickle state in a privileged workflow, so a contributor can bypass code review with a crafted baseline commit. Add creation and update restrictions, leaving only the intended app/token as a bypass actor.

Useful? React with 👍 / 👎.

],
"bypass_actors": [
{
"actor_id": 4021464,
"actor_type": "Integration",
"bypass_mode": "always"
}
]
}
83 changes: 80 additions & 3 deletions scripts/action/analyze.sh
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,16 @@ analyze_sync() {

REQUIRES_FULL=true
if [ "$(printf '%s' "${FORCE_FULL:-false}" | tr '[:upper:]' '[:lower:]')" != true ]; then
# The baseline branch's tip is this branch's last analysis. Without one, the
# run below seeds from a saved ancestor or analyzes in full, and delivery
# creates the branch again.
if [ "${SYNC_STRATEGY:-}" = branch ]; then
local tip_entry
tip_entry="$(baseline_index "${REPOSITORY:-}" | awk '{print $1; exit}')"
if [ -z "$tip_entry" ] || ! restore_baseline "${REPOSITORY:-}" "$tip_entry" "$state"; then
echo "::notice::$BASELINE_BRANCH has no analysis to continue from; this sync creates it."
fi
fi
if [ "$(depth_cap_from "$state/analysis.json")" = "$DEPTH_CAP" ]; then
incremental "$CHECKOUT_DIR" "$state"
fi
Expand Down Expand Up @@ -264,6 +274,54 @@ keep_user_config() {
done
}

# sync_strategy: branch keeps one commit per sync on BASELINE_BRANCH, each with a
# CodeBoarding-Source trailer naming the commit it analysed. Lists them as
# "<branch commit> <source sha>", newest first, at most BASELINE_DEPTH of them.
# Fetched without blobs into a scratch repository: the lookup needs messages, and
# a hundred pickles would cost more than it saves.
BASELINE_DEPTH="${BASELINE_DEPTH:-100}"
baseline_index() {
local repository="$1" scratch="$RUNNER_TEMP/codeboarding-baseline-index.git" auth
[ -n "${BASELINE_BRANCH:-}" ] || return 0
rm -rf "$scratch"
git init -q --bare "$scratch"
auth="$(printf 'x-access-token:%s' "${GIT_TOKEN:-}" | base64 -w0)"
git -C "$scratch" -c "http.extraheader=AUTHORIZATION: basic $auth" fetch -q --filter=blob:none \
--depth="$BASELINE_DEPTH" "${GITHUB_SERVER_URL%/}/${repository}.git" "refs/heads/$BASELINE_BRANCH" 2>/dev/null ||
return 0
git -C "$scratch" log --format='%H %(trailers:key=CodeBoarding-Source,valueonly,separator=%x20)' FETCH_HEAD |
awk 'NF == 2'
}
# Lays a baseline-branch commit's analysis over $3, which keeps the configuration
# seeded from the checkout. source.json is provenance, not engine state.
restore_baseline() {
local repository="$1" commit="$2" state="$3" scratch="$RUNNER_TEMP/codeboarding-baseline-restore"
fetch_commit "$repository" "$commit" || return 1
rm -rf "$scratch"
mkdir -p "$scratch" "$state"
git -C "$CHECKOUT_DIR" archive "$commit" .codeboarding | tar -x -C "$scratch" || return 1
rm -f "$scratch/.codeboarding/source.json"
cp -a "$scratch/.codeboarding/." "$state/"

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 Replace generated state when restoring the branch

When a repository switches from push to branch, $state is initially seeded with the old committed .codeboarding directory, and this overlay leaves any generated files absent from the branch snapshot untouched. Optional artifacts such as static_analysis.sha, codeboarding_version.json, or a health report can therefore come from the old code-branch baseline while analysis.json and the pickle come from the baseline branch, and that mixed state is passed to incremental analysis. Clear the generated state before copying the snapshot, then restore only the current checkout's user configuration as seed_from_ancestor does.

Useful? React with 👍 / 👎.

}
# Seeds $3 from the baseline branch's entry for $2, or for its nearest first-parent
# ancestor that has one. Sets BRANCH_SOURCE to the commit it describes and
# BRANCH_DISTANCE to how far below $2 that is.
BRANCH_SOURCE="" BRANCH_DISTANCE=""
seed_from_baseline_branch() {
local repository="$1" tip="$2" state="$3" index commit entry="" distance=0
BRANCH_SOURCE="" BRANCH_DISTANCE=""
index="$(baseline_index "$repository")"
[ -n "$index" ] || return 1
fetch_commit "$repository" "$tip" "$(( CATCHUP_BOUND + 1 ))" || true
for commit in $(git -C "$CHECKOUT_DIR" rev-list --first-parent --max-count=$(( CATCHUP_BOUND + 1 )) "$tip" 2>/dev/null); do
entry="$(awk -v source="$commit" '$2 == source {print $1; exit}' <<< "$index")"
[ -z "$entry" ] || break
distance=$(( distance + 1 ))
done
[ -n "$entry" ] && restore_baseline "$repository" "$entry" "$state" || return 1
BRANCH_SOURCE="$commit" BRANCH_DISTANCE="$distance"
}

# Rewrites the sticky progress comment while the base is built from scratch. A
# fork's read-only token makes every call fail, which costs nothing.
PROGRESS_PID=""
Expand Down Expand Up @@ -318,11 +376,12 @@ analyze_review() {
# needs no engine run at all. Without one, the merge base is checked out and
# analyzed from whatever baseline the repository committed there. Each path
# records how the base was obtained, for the comment and the review artifact.
local base_started base_source=saved base_reason="" base_from_sha="" catchup_commits=""
local base_started base_source=saved base_reason="" base_from_sha="" catchup_commits="" base_was_published=false
base_started="$(date +%s)"
if [ "$(depth_cap_from "${BASE_DIR:-}/analysis.json")" = "$DEPTH_CAP" ]; then
mkdir -p "$base_state"
cp -a "$BASE_DIR/." "$base_state/"
base_was_published=true
base_from_sha="$REVIEW_BASE_SHA" catchup_commits=0
else
# A bundle under this exact name that the run cannot use was made with another cap.
Expand All @@ -344,6 +403,23 @@ analyze_review() {
elif [ -f "$base_state/analysis.json" ]; then
base_reason=incompatible
fi
# The baseline branch: its entry for the merge base is that commit's own
# analysis, and an entry for an ancestor is caught up like a committed one.
if [ "$REQUIRES_FULL" = true ] && seed_from_baseline_branch "$REVIEW_BASE_REPO" "$REVIEW_BASE_SHA" "$base_state"; then
if [ "$(depth_cap_from "$base_state/analysis.json")" != "$DEPTH_CAP" ]; then
base_reason=incompatible
elif [ "$BRANCH_DISTANCE" -eq 0 ]; then
REQUIRES_FULL=false base_source=saved base_from_sha="$BRANCH_SOURCE" catchup_commits=0
Comment on lines +411 to +412

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 Reject exact baselines from incompatible configurations

When the baseline branch contains an entry for the merge base, this path accepts it after checking only depth_cap and skips the compatibility run. Baseline artifacts are otherwise keyed by CFG_HASH, which also covers the engine version, provider, endpoint, region, and model inputs; after an engine upgrade or when sync and review use different models, the review therefore compares an old-configuration base with a newly generated head and can report spurious architecture changes. Store and validate the configuration identity with each branch entry, or run the same compatibility check used for non-exact entries before treating it as saved.

Useful? React with 👍 / 👎.

else
incremental "$base_checkout" "$base_state"
if [ "$REQUIRES_FULL" = true ]; then
base_reason=incompatible
else
base_source=ancestor base_from_sha="$BRANCH_SOURCE"
catchup_commits="$(catchup_count "$BRANCH_SOURCE" "$REVIEW_BASE_SHA")"
fi
fi
fi
# Nothing at the merge base to grow from: catch up from the nearest saved
# ancestor, and publish the result under the merge base's own name below.
if [ "$REQUIRES_FULL" = true ] && seed_from_ancestor "$REVIEW_BASE_REPO" "$REVIEW_BASE_SHA" "$base_state" false "$base_checkout"; then
Expand Down Expand Up @@ -403,9 +479,10 @@ analyze_review() {
# under the same name every run, so normally only a run that produced one
# publishes it. The exception is lifetime: a review artifact references a base
# by id for its whole retention, so one about to expire is renewed rather than
# left dangling under a review that outlives it.
# left dangling under a review that outlives it. A base read from the baseline
# branch is published too: no artifact holds it yet.
local publish_base=false
if [ "$base_source" != saved ] || [ "${RENEW_BASE:-false}" = true ]; then
if [ "$base_was_published" != true ] || [ "${RENEW_BASE:-false}" = true ]; then
stage "$base_state" base
publish_base=true
fi
Expand Down
66 changes: 66 additions & 0 deletions scripts/action/deliver-sync.sh
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,72 @@ classify_push_failure() {
exit 1
}

# sync_strategy: branch keeps the analysis on an orphan branch of its own, one
# fast-forward commit per sync, and never writes to the target branch.
deliver_to_baseline_branch() {
local branch="$BASELINE_BRANCH" tree="$RUNNER_TEMP/codeboarding-baseline-tree"
local index="$RUNNER_TEMP/codeboarding-baseline-index" git_dir files new_tree tip parent commit now
git_dir="$(git rev-parse --absolute-git-dir)"
rm -rf "$tree" "$index"
mkdir -p "$tree"
CHECKOUT_DIR="$tree" "$ACTION_PATH/scripts/action/install-sync.sh" > /dev/null
files="$(find "$tree/.codeboarding" -maxdepth 1 -type f | wc -l | tr -d ' ')"
# Engine output is never edited; which commit it describes lives here only.
python3 -c 'import datetime,json,os,sys
json.dump({
"schema": 1,
"source_branch": os.environ["TARGET_BRANCH"],
"source_sha": sys.argv[2],
"generated_at": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"engine_version": os.environ.get("ENGINE_VERSION", ""),
}, open(sys.argv[1], "w"), indent=2)' "$tree/.codeboarding/source.json" "$BASE_SHA"
GIT_INDEX_FILE="$index" git --git-dir="$git_dir" --work-tree="$tree" -C "$tree" add -A -f .codeboarding
new_tree="$(GIT_INDEX_FILE="$index" git --git-dir="$git_dir" write-tree)"
git config user.name 'codeboarding-review[bot]'
git config user.email 'codeboarding-review[bot]@users.noreply.github.com'

# Two tries: a concurrent sync that moved the branch for an older commit is
# built on top of once. A second move means a newer run is handling it.
for _ in 1 2; do
git fetch -q "$REMOTE" "$TARGET_BRANCH"
if [ "$(git rev-parse FETCH_HEAD)" != "$BASE_SHA" ]; then
emit_result "$files" false "$BASE_SHA"
echo "::notice::$TARGET_BRANCH advanced during analysis; a newer run should update $branch."
exit 0
fi
tip="$(git ls-remote "$REMOTE" "refs/heads/$branch" | awk '{print $1; exit}')"
parent=()
if [ -n "$tip" ]; then
git fetch -q --depth=1 "$REMOTE" "refs/heads/$branch"
Comment on lines +99 to +102

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 Use the fetched tip as the baseline parent

If another sync advances the baseline branch after ls-remote records tip but before this depth-one fetch, the fetch downloads the new tip while parent still names the old one. When that old commit is not already in the checkout—such as with force_full, or when the branch had already moved during analysis—git commit-tree -p "$tip" fails before the push and the advertised retry logic can run. git fetch -h describes --depth as producing shallow history, so derive tip from the fetched FETCH_HEAD or fetch the recorded SHA explicitly.

Useful? React with 👍 / 👎.

parent=(-p "$tip")
Comment on lines +101 to +103

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 Refuse to overwrite an existing non-baseline branch

If baseline_branch already names any ordinary branch other than target_branch—for example a pre-existing codeboarding/baseline, develop, or the old codeboarding/sync branch—this unconditionally makes its tip the parent of a commit whose tree contains only .codeboarding/. The subsequent fast-forward push succeeds and makes every other file disappear from that branch, rather than creating the promised orphan branch. Verify that an existing tip is a recognized CodeBoarding baseline before parenting it, and fail without pushing when it is not.

Useful? React with 👍 / 👎.

if [ "$(git log -1 --format='%(trailers:key=CodeBoarding-Source,valueonly)' "$tip" | tr -d '[:space:]')" = "$BASE_SHA" ] &&
git diff --quiet -I '"generated_at"' -I '"timestamp"' "$tip" "$new_tree"; then
emit_result "$files" false "$BASE_SHA"
echo "::notice::$branch already holds this analysis of $TARGET_BRANCH @${BASE_SHA:0:7}."
exit 0
fi
fi
commit="$(git commit-tree "$new_tree" ${parent[@]+"${parent[@]}"} \
-m "chore(codeboarding): diagram of $TARGET_BRANCH @${BASE_SHA:0:7}" \
-m "CodeBoarding-Source: $BASE_SHA")"
# Never forced: the parent is the tip just read, so this only ever fast-forwards.
if git push -q "$REMOTE" "$commit:refs/heads/$branch"; then
Comment on lines +114 to +115

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 Couple the target-head check to the baseline update

The target branch is checked by a separate fetch at the start of the loop, but this push updates only the baseline ref. If target_branch advances after that fetch and before this push, the push still succeeds and publishes an analysis of the old BASE_SHA as the latest baseline, contrary to the stale-result guarantee; the queued newer run may not repair it if that run later fails or is cancelled. The expected target SHA needs to be validated in the same remote transaction as the baseline update.

Useful? React with 👍 / 👎.

emit_result "$files" true "$BASE_SHA"
echo "baseline_branch_sha=$commit" >> "$GITHUB_OUTPUT"
exit 0
fi
now="$(git ls-remote "$REMOTE" "refs/heads/$branch" | awk '{print $1; exit}')"
if [ "$now" = "$tip" ]; then
echo "::error::GitHub refused the push to $branch, most likely because a branch rule protects it. Add the CodeBoarding app as a bypass actor for $branch in the repository's rulesets, or set sync_strategy: push."
exit 1
fi
done
emit_result "$files" false "$BASE_SHA"
echo "::notice::Another sync keeps updating $branch; leaving it to that run."
exit 0
}
[ "$SYNC_STRATEGY" != branch ] || deliver_to_baseline_branch

"$ACTION_PATH/scripts/action/install-sync.sh" > "$GENERATED_PATHS"
stage_paths=()
while IFS= read -r path; do
Expand Down
Loading
Loading