Fresh Jots for GitHub Actions
One step in any workflow appends a line to a note you can read on your
phone — a deploy log, a red-build alert, or a nightly job that emails you
if it ever stops running. No runtime to install, no curl to
hand-write.
- id: ts
run: echo "at=$(date -u +%FT%TZ)" >> "$GITHUB_OUTPUT"
shell: bash
- uses: Goran-Arsov/freshjots-append@v1
with:
note: deploys-prod
text: "${{ steps.ts.outputs.at }} ${{ github.repository }} deployed ${{ github.sha }} (${{ job.status }})"
env:
FRESHJOTS_TOKEN: ${{ secrets.FRESHJOTS_TOKEN }}
What you get
One step, zero install
The append is a single uses: line — plus a
one-line date step when you want a timestamp
on each entry. The note is created on the first run and appended on
every run after — nothing to
apt install, no token juggling in shell.
A log you'll actually read
Each run adds one line to a note addressed by filename. Read it in the browser, on the installable phone app, through the REST API, or hand it to your AI over MCP — it outlives the Actions run log and isn't buried three clicks deep.
Append-only by default
The Action creates the note append-only, so entries are added and never rewritten — the log reads as a true chronological record of what your pipeline did.
A watchdog for free
Add one input and a scheduled workflow becomes its own dead-man's switch: if the run stops firing, Fresh Jots emails you — the uptime-monitor pattern, with no extra service.
Before it works
Three one-time things before the first workflow run can write to your notebook — then you add the append step to a workflow file in each repo you want to log (below).
- A Fresh Jots account on the Dev tier or higher. API access — including this Action — is on the Dev and Team plans; the free and Personal plans are web-only. New accounts get a 14-day Dev API trial, so you can wire a real workflow before paying anything.
-
An API token, minted at
/settings/api_tokens. For CI, choose the
write-only scope and use
Limit to a single note to bind it to the one note you'll append to,
so a leaked CI token can only write to that notebook — never overwrite or
delete the rest of your account. That list shows only notes that already
exist and is greyed out until you have one, so create the note first
(a plain note on freshjots.com) and name it to match your workflow's
note:value, e.g.ci-log. -
Store the token as a GitHub Actions secret named exactly
FRESHJOTS_TOKEN. In the consuming repo: Settings → Secrets and variables → Actions → New repository secret. For many repos, store it once at the org level and grant each repo access instead.
Skipping the note-binding? Then the note doesn't need to exist first — the Action creates it on the first append. But an unrestricted write-only token can write to, overwrite, or delete any of your notes, so binding it to one note is the safer default.
Add the step to a workflow — in each repo
A GitHub Actions workflow is a YAML file inside a
.github/workflows/ folder in your
repository. The snippet below is a single step: add it to a workflow you already
have, or create a new file (below). You add it by hand in each repo you want to log —
there is no central switch that turns it on everywhere, and Fresh Jots never edits your repos for you.
The one-line ts step stamps a UTC timestamp —
GitHub has no expression for the current time, so a tiny
date step is the reliable way to get one.
${{ github.repository }},
${{ github.sha }}, and
${{ job.status }} are standard GitHub
context — the appended line is just a string, so put whatever you'd want
to see on your phone in it.
Already have a workflow? Add this step to one of its jobs:
- id: ts
run: echo "at=$(date -u +%FT%TZ)" >> "$GITHUB_OUTPUT"
shell: bash
- uses: Goran-Arsov/freshjots-append@v1
with:
note: deploys-prod
text: "${{ steps.ts.outputs.at }} ${{ github.repository }} deployed ${{ github.sha }} (${{ job.status }})"
env:
FRESHJOTS_TOKEN: ${{ secrets.FRESHJOTS_TOKEN }}
Starting from nothing? Create the file
.github/workflows/freshjots.yml
in the repo with exactly this — it appends one line to a note on every push:
# .github/workflows/freshjots.yml
name: Fresh Jots log
on:
push:
branches: [ main ] # your repo's default branch
jobs:
log:
runs-on: ubuntu-latest
steps:
- id: ts
run: echo "at=$(date -u +%FT%TZ)" >> "$GITHUB_OUTPUT"
shell: bash
- uses: Goran-Arsov/freshjots-append@v1
with:
note: ci-log
text: "${{ steps.ts.outputs.at }} ${{ github.repository }} pushed ${{ github.sha }}"
env:
FRESHJOTS_TOKEN: ${{ secrets.FRESHJOTS_TOKEN }}
Check it worked
Commit and push (or trigger the workflow), then watch the run under your
repository's Actions tab. A green check means the
line was appended — open the note named in note:
(e.g. ci-log) on freshjots.com, the
phone app, or the API and you'll see it. The note is created on the first
successful append if it didn't exist.
If the append step fails (a red ✕), open its log in the run — the message is one of:
FRESHJOTS_TOKEN env var is required— the secret is missing or misnamed. It must be exactlyFRESHJOTS_TOKEN, and GitHub withholds secrets from pull requests opened off a fork.append failed (HTTP 401)— the token is wrong, expired, or revoked. Mint a fresh one and update the secret.append failed (HTTP 403)— the account isn't on the Dev tier, or the 14-day trial ended. API writes need Dev.
Turn a scheduled job into its own watchdog
Add the append-deadline-hours input and
if: always() so the step runs whether the
job passed or failed. The first run creates the note, sets the deadline,
and writes the first line.
on:
schedule:
- cron: "0 3 * * *" # nightly at 03:00 UTC
jobs:
backup:
runs-on: ubuntu-latest
steps:
- run: ./backup.sh
- id: ts
if: always() # stamp the entry even if the backup failed
run: echo "at=$(date -u +%FT%TZ)" >> "$GITHUB_OUTPUT"
shell: bash
- uses: Goran-Arsov/freshjots-append@v1
if: always() # log success and failure alike
with:
note: nightly-backup
text: "${{ steps.ts.outputs.at }} ${{ github.repository }} backup ${{ job.status }} (${{ github.sha }})"
append-deadline-hours: 26 # alert me if no run within 26h
env:
FRESHJOTS_TOKEN: ${{ secrets.FRESHJOTS_TOKEN }}
If the workflow ever stops firing — the runner is down, the schedule got disabled, or an earlier step broke the job before it reached this one — Fresh Jots emails you within about ten minutes of the deadline lapsing. Same primitive that Healthchecks.io built a business around, included on the Dev tier.
More recipes
CI and deploys in one repo → two notes
Testing and shipping are two different pipelines, so give each its own note. Your CI workflow runs on every push and pull request — most of those lines come from branches that never ship. Your deploy workflow runs only when something reaches production. Keep them in separate notes and each stays readable: one answers “did this branch pass?”, the other is a clean record of “what's actually live.” Pour both into one note and the deploy history drowns in CI noise.
So you add two append steps — one in each workflow
file, each with a different note:.
The example names below are deploy-gh-ci
(the CI note) and deploy-gh-actions
(the deploy note) — rename them to whatever suits your repo, e.g.
myapp-ci and
myapp-deploy. The only
rule is that the two names differ.
1. The CI result — in your CI workflow
(the file that runs your tests, e.g.
.github/workflows/ci.yml), paste this as the
last step of the job that runs your tests.
if: always() makes it log red builds too:
- id: ts
if: always()
run: echo "at=$(date -u +%FT%TZ)" >> "$GITHUB_OUTPUT"
shell: bash
- uses: Goran-Arsov/freshjots-append@v1
if: always()
with:
note: deploy-gh-ci
text: "${{ steps.ts.outputs.at }} ${{ github.repository }} CI ${{ job.status }} on ${{ github.ref_name }} @ ${{ github.sha }}"
env:
FRESHJOTS_TOKEN: ${{ secrets.FRESHJOTS_TOKEN }}
If your CI is several separate jobs, put this in a small final job that
needs: all the others, so it reports the
overall result rather than one job's.
2. The deploy result — in your deploy workflow
(e.g. .github/workflows/deploy.yml), paste this as the
last step of the deploy job, right after the step that deploys.
if: always() logs a failed deploy as well:
- id: ts
if: always()
run: echo "at=$(date -u +%FT%TZ)" >> "$GITHUB_OUTPUT"
shell: bash
- uses: Goran-Arsov/freshjots-append@v1
if: always()
with:
note: deploy-gh-actions
text: "${{ steps.ts.outputs.at }} ${{ github.repository }} ${{ github.sha }} — deploy ${{ job.status }}"
env:
FRESHJOTS_TOKEN: ${{ secrets.FRESHJOTS_TOKEN }}
Scoping a write-only token to a single note (the safer setup above)? Two notes then need two such tokens — one bound to each — or a single token bound to neither. Either way, create both notes first if you're binding.
Only when a deploy fails
- id: ts
if: failure()
run: echo "at=$(date -u +%FT%TZ)" >> "$GITHUB_OUTPUT"
shell: bash
- uses: Goran-Arsov/freshjots-append@v1
if: failure()
with:
note: failed-deploys
text: "${{ steps.ts.outputs.at }} ${{ github.repository }} ${{ github.workflow }} ${{ github.ref_name }} failed @ ${{ github.sha }}"
env:
FRESHJOTS_TOKEN: ${{ secrets.FRESHJOTS_TOKEN }}
A separate log per environment
- id: ts
run: echo "at=$(date -u +%FT%TZ)" >> "$GITHUB_OUTPUT"
shell: bash
- uses: Goran-Arsov/freshjots-append@v1
with:
note: deploys-${{ inputs.environment }}
text: "${{ steps.ts.outputs.at }} ${{ github.repository }} deployed ${{ github.sha }} to ${{ inputs.environment }}"
env:
FRESHJOTS_TOKEN: ${{ secrets.FRESHJOTS_TOKEN }}
Inputs & output
note(required) — filename of the notebook, e.g.deploys-prod. Created on first append.text(required) — the line to append. Stored verbatim: Fresh Jots adds no timestamp of its own, so put the timestamp and repo in the string, as the examples above do. Newlines are kept; convention is one line per run.append-deadline-hours(optional, 1–720) — dead-man's-switch deadline; re-set on every run (idempotent).api-url(optional) — defaults tohttps://freshjots.com/api/v1; override only for self-hosted instances.note-id(output) — numeric id of the note that received the append.
Authentication is the FRESHJOTS_TOKEN env var, above.
Pin a version
@v1 always tracks the latest 1.x release
(non-breaking updates). Pin @v1.0.1 or a commit
SHA for full reproducibility. The Action is MIT-licensed — see the
GitHub Marketplace listing
and the
freshjots-append repo.