Skip to content

keepalive: avoid diverging commits in forks#20

Open
dscho wants to merge 4 commits into
gitgitgadget:mainfrom
dscho:fix-keepalive-in-forks-again
Open

keepalive: avoid diverging commits in forks#20
dscho wants to merge 4 commits into
gitgitgadget:mainfrom
dscho:fix-keepalive-in-forks-again

Conversation

@dscho

@dscho dscho commented Jan 9, 2026

Copy link
Copy Markdown
Member

Ideally, we do not diverge in forks of gitgitgadget-workflows, but committing to keep the scheduled workflows alive is prone to cause exactly that.

Instead, disable and immediately re-enable the scheduled workflows (including the keepalive one itself). That also re-sets the 60-day timer.

Keep the automatic merge from upstream as it is most likely desirable. Should the need arise for a downstream fork to opt-out of that behavior, it is easy enough to add a mechanism for that, once (or: if) that need arises.

@dscho dscho requested a review from webstech January 9, 2026 18:08
@dscho dscho self-assigned this Jan 9, 2026
@dscho dscho force-pushed the fix-keepalive-in-forks-again branch from 6f70cff to b8d1227 Compare January 10, 2026 10:38
Comment thread .github/workflows/keepalive.yml Outdated
test 0 -lt $(git rev-list --count --since=3.weeks.ago HEAD)
test 0 -lt $(git rev-list --count --since=${{ github.event.repository.fork == true && '4' || '3' }}.weeks.ago HEAD)
then
echo "::notice::No need to keep alive, there were commits in the last three weeks"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should this message be more specific? If I understand this correctly (stop laughing!) the notice will be issued more frequently for four weeks.

@dscho dscho force-pushed the fix-keepalive-in-forks-again branch from b8d1227 to 52a0571 Compare July 6, 2026 11:21
@dscho

dscho commented Jul 6, 2026

Copy link
Copy Markdown
Member Author

@webstech I pivoted rather dramatically to a totally different strategy. How do you like it?

@dscho dscho requested a review from webstech July 6, 2026 11:24

@webstech webstech left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I like the change in direction. Aside from the other comments, is it known if the state will reset to disabled_inactivity on a predictable frequency? Just wondering if cycling disable/enable will reset the inactivity clock. Hmmm, should the script check for that state as well as active?

Comment thread .github/workflows/keepalive.yml Outdated
name=${path##*/}
echo "Refreshing $name"
gh api -XPUT "/repos/$REPO/actions/workflows/$name/disable" &&
gh api -XPUT "/repos/$REPO/actions/workflows/$name/enable" || {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Any reason for not using the gh workflow enable command?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Huh, good point. I was imitating gh-workflow-immortality.sh and never thought that maybe the GitHub CLI sprouted a command for that... Fixed!

Comment thread .github/workflows/keepalive.yml Outdated
echo "Refreshing $name"
gh api -XPUT "/repos/$REPO/actions/workflows/$name/disable" &&
gh api -XPUT "/repos/$REPO/actions/workflows/$name/enable" || {
echo "::error::could not refresh $name" >&2

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could this be a little more assertive? Maybe suggest making sure the workflow is not disabled?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes!

dscho added 4 commits July 7, 2026 08:36
GitHub disables scheduled workflow triggers after 60 days without
repository activity. The current workaround commits to
`.github/cached/keepalive.txt` every three weeks, which forces every
downstream fork (e.g. `cygwingitgadget/gitgitgadget-workflows`) to
either diverge from upstream or merge the churn back on a schedule.

A much cleaner mechanism exists:

  PUT /repos/{owner}/{repo}/actions/workflows/{id}/enable

resets the 60-day timer even when the workflow is already enabled. The
trick is documented in
https://github.com/PhrozenByte/gh-workflow-immortality, and it works
against forks like `cygwingitgadget/gitgitgadget-workflows`, without
conflicting with upstream changes.

That endpoint is not reachable with the default `GITHUB_TOKEN`, so this
reuses the `actions/create-github-app-token` pattern (and the per-fork
`GITGITGADGET_GITHUB_APP_*` secrets) that the other workflows in this
repo already rely on.

Only workflows in state `active` are refreshed; anything a fork has
deliberately disabled (e.g. `sync-ref.yml` in the Cygwin fork, which has
no sister repo to sync from) stays disabled.

Assisted-by: Opus 4.7
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Calling `enable` on a workflow that is already `active` is not
documented to reset GitHub's internal 60-day inactivity clock; the REST
documentation at https://docs.github.com/en/rest/actions/workflows
leaves the semantics of a no-op call unspecified. Bouncing through
`active` -> `disabled_manually` -> `active` forces a real state
transition and removes the ambiguity.

The `&&` chaining is deliberate: GitHub Actions runs shell steps with
`set -e`, so if either call fails the loop exits and the step is marked
failed loudly, rather than silently leaving a workflow in
`disabled_manually`.

Assisted-by: Opus 4.7
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Now that the previous commit uses the `PUT
.../actions/workflows/{id}/enable` endpoint to reset the 60-day
scheduled-workflow inactivity timer directly, the older fallback that
invented a commit under `.github/cached/` has no remaining purpose. In
every fork it either did nothing or produced a divergent commit that
then had to be pushed; both outcomes are strictly worse than the
enable-endpoint approach.

The upstream-merge step is kept deliberately: its purpose (keeping forks
close to upstream) is orthogonal to the inactivity timer, and its push
retry loop still applies to the merge result.

Assisted-by: Opus 4.7
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
This is now no longer needed.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
@dscho dscho force-pushed the fix-keepalive-in-forks-again branch from 52a0571 to 7f46067 Compare July 7, 2026 06:47
@dscho

dscho commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

is it known if the state will reset to disabled_inactivity on a predictable frequency?

True to form, GitHub's documentation does not lose any words about any interesting aspect of this behavior. The description of this behavior is hidden in the documentation how to disable/enable workflows. Here is the full text, in all of its dubitable glory:

Warning

To prevent unnecessary workflow runs, scheduled workflows may be disabled automatically. When a public repository is forked, scheduled workflows are disabled by default. In a public repository, scheduled workflows are automatically disabled when no repository activity has occurred in 60 days.

It talks about "repository activity". Now, you might think that opening a PR, pushing a new branch or tag, or commenting on an issue would count as activity. Nope. It's not documented anywhere, but experiments demonstrated that those activities don't count as activities. Which is curious because GitHub itself calls pushes to all branches activity in URLs like https://github.com/gitgitgadget/gitgitgadget/activity. BTW I did not conduct those experiments, I lack that kind of patience to suffer through iterations that cost 60 days each, just because somebody could not be bothered to document their product properly.

Just wondering if cycling disable/enable will reset the inactivity clock.

I only have the lore of git-workflow-immortality at hand to state that enabling workflows (even already-enabled ones) re-sets that clock.

Hmmm, should the script check for that state as well as active?

It would be nice, wouldn't it? But that clock isn't surfaced anywhere in the API nor UI. For example, you might think that getting all the information for a given workflow would show that clock in some attribute, right? You'd be wrong:

$ (REPO=gitgitgadget/gitgitgadget-workflows; name=keepalive.yml; gh api "/repos/$REPO/actions/workflows/$name")
{
  "id": 173723163,
  "node_id": "W_kwDOHv049s4KWs4b",
  "name": "Keep scheduled workflows alive",
  "path": ".github/workflows/keepalive.yml",
  "state": "active",
  "created_at": "2025-07-10T10:27:12.000+02:00",
  "updated_at": "2025-07-10T10:27:12.000+02:00",
  "url": "https://api.github.com/repos/gitgitgadget/gitgitgadget-workflows/actions/workflows/173723163",
  "html_url": "https://github.com/gitgitgadget/gitgitgadget-workflows/blob/main/.github/workflows/keepalive.yml",
  "badge_url": "https://github.com/gitgitgadget/gitgitgadget-workflows/workflows/Keep%20scheduled%20workflows%20alive/badge.svg"
}

No sign of me disabling/re-enabling it a couple of minutes ago using gh workflow to verify that it works.

@dscho

dscho commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

BTW here is a test run in my fork, where I temporarily enabled some workflows to show that they do get refreshed.

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