Sources
Repodar starts from what a repository declares in its files and then lets the platforms that
actually run it correct the picture. A Laravel app that says "php": "^8.2" in composer.json but
runs on PHP 8.4 on Forge is shown, and EOL-checked, as PHP 8.4.
Every value carries a confidence. When two sources disagree, the higher one wins:
| Confidence | Comes from | Example |
|---|---|---|
observed |
your server's own report, or a platform API (Forge, Vercel, DigitalOcean) | the server reports PHP 8.5.12; Forge site runs PHP 8.4 |
pinned |
an explicit version file | .nvmrc says 22 |
ci |
a GitHub Actions setup step | actions/setup-node with node-version: 20 |
declared-minimum |
a manifest constraint | composer.json requires ^8.2, shown as 8.2 |
declared-minimum values get a small badge in the dashboard: the project allows that version but
might run a newer one. The deploy target follows the same idea, except that a manual override on
the repository page always wins.
How precise a version is
A version is shown as precisely as its source states it, and never more precisely:
.php-version with 8.5.12 shows PHP 8.5.12, FROM node:22-alpine shows Node.js 22, Forge's
php85 shows PHP 8.5, and "php": "~8.3.1" shows ≥ 8.3.1. A leading v is dropped. A
prerelease (8.5.0RC1, 8.5-rc) is shown as its major.minor (8.5), and aliases such as
lts/iron or latest are skipped with a warning.
End-of-life checks, finding names and severities always use the major.minor release cycle
(8.5.12 → 8.5), so learning an exact version, or a patch upgrade, never opens, closes or renames a
finding, and the digest reports a runtime change only when the cycle changes. Two sources that
agree on the digits they both state (a 8.5.12 pin and a CI step on 8.5) do not count as a
conflict. The chip's tooltip says where the version came from.
An observed version still wins over the repository files, but it does not throw away precision
the repository adds: when a platform reports only 8.5 and .php-version pins 8.5.12 (or a CI
step sets up 8.5.12), the chip shows 8.5.12 as observed, with the tooltip "observed on Forge … ·
exact version pinned in .php-version". A server report beats everything, then
the command Forge runs, then the site's own response
headers, then a pin, then CI. A pin on another cycle (8.4.2 while Forge runs 8.5) is ignored: the platform is what
runs.
Repository files
Always on, no setup. Uses the GitHub App's Contents permission (or the token for URL-added repos).
- Reads: from the repository root only (plus the optional per-repo scan paths for a
monorepo): the files in the runtime table below, the lockfiles in the
dependency table,
.tool-versions,Dockerfile,wrangler.toml|json|jsonc,serverless.yml,cdk.json,samconfig.toml,template.yaml,.do/app.yaml,vercel.json. Each scanned folder is listed once and only the files the listing shows are fetched (a few requests per repository);.github/workflowsis listed only when.githubexists. When a listing fails or has 1,000 entries (GitHub's cap), that folder falls back to asking for every known file. .NET project files (*.csproj,*.fsproj,*.vbproj) are taken from the repository root's listing only (at most 5, 200 KB each): a nested layout (src/App/App.csproj) is covered byglobal.json, the Dockerfile and CI instead. Scan paths do not read project files either. Nothing is cloned and nothing is executed; each file is parsed in memory and discarded. - Sets: runtimes with their version and confidence, frameworks with the version from the lockfile, dependencies with versions (dev-only ones flagged), and the deploy target.
- Runtime precedence, per runtime: a server or platform observation (
observed, see below), then a pin file (pinned), then a CI setup step (ci), then the minimum of a manifest constraint (declared-minimum, shown with a small≥). Two pins that disagree, or a pin and CI on another version, add a warning; the first pin in the table's order wins. - Deploy target, first match wins: a
wranglerconfig means Cloudflare;serverless.yml,cdk.json,samconfig.tomlor an AWS SAMtemplate.yamlmean AWS;.do/app.yamlmeans DigitalOcean;vercel.jsonmeans Vercel. Then a deploy hint from a workflow (below). ADockerfilewith nothing else means a container. Otherwise unknown. - Not read:
yarn.lock,Pipfile.lock, Maven and Gradle dependency trees,deno.json(it has no runtime version), nested packages outside the scan paths.
Supported runtimes
Every runtime below gets a chip, an end-of-life check, the lang: filter and its own version key
(python:3.12). The version is shown as precisely as the file states it; the cycle is what
end-of-life, severity, finding keys and the digest's "runtime changed" key on. A file that names
no concrete version is skipped: a release channel (stable, nightly, latest, lts/*) in a
CI step or rust-toolchain file with a note, anything unreadable with a warning.
| Runtime | Pins (in precedence order), then CI, then declared minimum | endoflife.date product | Cycle |
|---|---|---|---|
| PHP | .php-version, .tool-versions php, FROM php:; shivammathur/setup-php; composer.json require.php |
php |
major.minor (8.5) |
| Node.js | .nvmrc, .node-version, .tool-versions nodejs, FROM node:; actions/setup-node; package.json engines.node |
nodejs (cycles are majors) |
major.minor (22.11), matched to the major |
| Go | .tool-versions golang, FROM golang:, go.mod toolchain / go; actions/setup-go |
go |
major.minor (1.22) |
| Bun | .bun-version, .tool-versions bun, FROM oven/bun:; oven-sh/setup-bun; package.json engines.bun, else packageManager: "bun@…" |
bun |
major (1) |
| Deno | .dvmrc, .tool-versions deno, FROM denoland/deno: (also alpine-… tags); denoland/setup-deno |
deno (Deno 2 per minor, Deno 1 as 1) |
major.minor (2.1) |
| Python | .python-version, runtime.txt (python-3.12.3), Pipfile python_full_version (exact) or python_version (the cycle pipenv enforces), .tool-versions python, FROM python:; actions/setup-python; pyproject.toml requires-python (else Poetry's python, also { version = … }) |
python |
major.minor (3.12) |
| Ruby | .ruby-version (3.3.0, ruby-3.3.0), Gemfile ruby "3.3.0", Gemfile.lock RUBY VERSION, .tool-versions ruby, FROM ruby:; ruby/setup-ruby; Gemfile ruby ">= 3.2" / "~> 3.2" |
ruby |
major.minor (3.3) |
| Java | .java-version, .sdkmanrc (java=21.0.2-tem), .tool-versions java (temurin-21.0.2), FROM eclipse-temurin: / openjdk: / amazoncorretto:; actions/setup-java; pom.xml maven.compiler.release / java.version / maven.compiler.source, build.gradle(.kts) toolchain / sourceCompatibility |
eclipse-temurin (there is no java product) |
major (21); 1.8 reads as 8 |
| .NET | global.json sdk.version, .tool-versions dotnet, FROM mcr.microsoft.com/dotnet/…:; actions/setup-dotnet; the lowest <TargetFramework(s)> (net8.0, netcoreapp3.1) in root *.csproj / Directory.Build.props |
dotnet (8, older 3.1) |
major.minor (8.0) |
| Rust | rust-toolchain.toml channel, rust-toolchain, .tool-versions rust, FROM rust:; dtolnay/rust-toolchain@1.78.0 or its toolchain input, actions-rust-lang/setup-rust-toolchain; Cargo.toml rust-version (chip only) |
rust |
major.minor (1.78); judged by releases behind |
Things worth knowing:
- Rust and Deno 2: endoflife.date supports only the newest release of each, so every older
release reads as end-of-life within weeks. Repodar does not raise an end-of-life finding for
them; it counts releases behind the newest instead, as a "versions behind" finding
(
major_behind:rust: "Rust 1.78 is 21 releases behind (newest 1.99)"). Rust: 4 or more releases behind is medium, 8 or more high (about six and twelve months). Deno 2: 2 or more medium, 4 or more high. Deno 1 is a real end-of-life line and keeps its end-of-life finding. Arust-versioninCargo.tomlis a minimum supported version that lags on purpose: it gets a chip and no finding; pin a toolchain to have it checked. - Java: Repodar reads end-of-life dates from Eclipse Temurin (Adoptium's OpenJDK builds). Other vendors (Oracle, Corretto, Zulu) support some versions longer or shorter.
- .NET:
global.jsonpins an SDK (8.0.300) while images and servers name a runtime (8.0.6); only a different major.minor counts as a conflict.netstandard2.xand .NET Framework targets (net48) are not runtime cycles and are skipped with a note. - Bun: a
bun.lockalone says which package manager installs packages, not that Bun runs the app, so it does not create a Bun runtime. - Limits of the readers: TOML (
pyproject.toml,Pipfile,Cargo.toml,rust-toolchain.toml, the Python and Rust lockfiles) goes through a small tolerant reader for just these keys; XML and Gradle scripts are read with patterns, never evaluated, so a version inherited from a parent POM, set ingradle.propertiesor a version catalog, or computed in code is not seen.
Dependencies and vulnerabilities
| Ecosystem | Read from | Advisories |
|---|---|---|
| Composer | composer.lock (packages-dev flagged dev) |
Packagist security advisories |
| npm | package-lock.json, pnpm-lock.yaml, bun.lock (one, dev flagged) |
OSV.dev |
| Go | go.mod |
OSV.dev |
| PyPI | poetry.lock (category = "dev" from Poetry before 1.5 flagged dev), uv.lock (registry packages), requirements.txt (exact == pins only) |
OSV.dev |
| RubyGems | Gemfile.lock GEM specs (Bundler records no groups, so nothing is dev) |
OSV.dev |
| crates.io | Cargo.lock (crates.io packages only) |
OSV.dev |
Not covered: Maven and Gradle dependencies (no lockfile to read), Pipfile.lock, NuGet, and
git or private-registry packages. Frameworks with end-of-life checks from these files: Django
(django in the Python lockfiles or requirements.txt), Rails (rails, else railties, in
Gemfile.lock) and Spring Boot (the spring-boot-starter-parent parent or the
spring-boot-dependencies BOM in pom.xml, the org.springframework.boot Gradle plugin).
GitHub Actions workflows
Always on, no setup. Same permission as the repository files.
- Reads: up to 20 files in
.github/workflows/(200 KB each). - Sets: runtime versions with confidence
cifromactions/setup-node(node-versionornode-version-file),shivammathur/setup-php(php-version),actions/setup-go(go-version), and since step 16actions/setup-python,ruby/setup-ruby,actions/setup-java,actions/setup-dotnet,oven-sh/setup-bun,denoland/setup-deno,dtolnay/rust-toolchain(the version may be the action's ref,@1.78.0) andactions-rust-lang/setup-rust-toolchain. A version matrix or a multi-line version list counts as its highest version, with a note; an input naming a file (ruby-version: .ruby-version) or a release channel (toolchain: stable,bun-version: latest) is skipped with a note, and a named file is read as a pin on its own. - Deploy hints:
cloudflare/wrangler-actionmeans Cloudflare,aws-actions/*AWS,digitalocean/action-doctlDigitalOcean,amondnet/vercel-actionor thevercelCLI Vercel, a Forge deploy-hook URL Forge,appleboy/ssh-actiona server,docker/build-push-actiona container,flyctlFly. A hint ranks below the platform files above.
Dockerfile base image
Always on. The OS under the FROM image (Debian, Alpine, Ubuntu, including tags such as
php:8.3-fpm-bookworm, which is Debian 12) is checked against endoflife.date like any runtime.
An unsupported release becomes an EOL finding named os:<distro>.
Repository activity
Always on. GitHub's pushed_at marks a repository stale after 180 days without a push, and
archived repositories are tagged as such (activity:stale, activity:archived in the filter).
Laravel Forge
- Needs: a Forge API token with
server:viewandsite:manage-commands. Addorganization:viewso Repodar can walk every organization; without it, it falls back to listing sites directly.site:manage-commandsis used for one thing only, the version command below; without it Repodar falls back to the site's headers. - Reads, once a day: organizations, servers and sites. Forge allows 60 requests a minute; Repodar uses one request per organization, per server and per page of sites, plus the version command (below).
- Sets: deploy target Forge, the site's PHP version as
observed(the server's default when the site has none; static and Node.js sites report no PHP), and the time of the last finished deployment. - Matching: the site's repository (
owner/name) against your repositories.
Exact versions from a command Forge runs
Forge's API reports PHP as major.minor only (php85 is PHP 8.5). So once a day Repodar asks Forge
to run one command on each site (POST …/sites/{site}/commands, in the site's directory, as the
site's user) and reads its output a minute later. The command is fixed text: the site's own PHP
binary, taken from Forge's PHP version for the site (php8.5; plain php when Forge names none),
then every other runtime it finds on the server's PATH:
php8.5 -r 'echo "php=".PHP_VERSION."\n";' 2>/dev/null; for c in node bun deno go python3 ruby java dotnet rustc; do command -v $c >/dev/null 2>&1 || continue; case $c in node) echo "node=$(node -v 2>/dev/null)";; … esac; done
It prints lines like php=8.5.12, node=v22.11.0, python=Python 3.12.3, and Repodar keeps
only the version numbers. What happens with them:
- PHP becomes the site's exact version ("observed on Forge site example.com · server web-1 · exact version from a command Forge ran on the server"), when it agrees with the major.minor Forge reports for the site. It beats the header probe below.
- Every other runtime is stored with the site. It refines a runtime the repository already
uses (its
.nvmrcsays22, the server runs22.11.0), and otherwise it is listed on the repository page under Also on this server, without a chip. - Every runtime gets EOL checks and chips (see supported runtimes): Python found on the server of a Django repository refines that repository's Python chip; Ruby found on the same server is listed under Also on this server unless the repository uses Ruby too.
Limits and fallbacks:
- At most once per site per day (a site asked in the last 20 hours is not asked again, so "Collect now" creates nothing new), at most 60 commands per poll, never-asked sites first, 15 seconds for creating them, and one collect of a workspace at a time creates them. A site with a fresh server report, or whose headers already gave an exact version, is not asked. The output is read in a separate run about 1, 3, 8 and 13 minutes after the create, until Forge reports the command finished (after the last read it is given up until the next day); at most two requests per command per attempt. Each runtime's output is cut to its first line.
- Forge keeps a visible entry in each site's command history for every run, so you will see one such entry per site per day.
- A non-zero exit, a timeout, output that is not a version, or a PHP that disagrees with Forge (another binary) is ignored: the version learned on an earlier day stays.
- The token lacks
site:manage-commands(Forge answers 403): Repodar stops trying for 24 hours and the Forge card says "Add thesite:manage-commandsscope to your Forge token to read exact PHP versions". Saving a new token tries again at once. When it works, the card shows "Exact PHP versions: on · last run 2 h ago · 3 sites".
Exact PHP version from the site's headers
Without the command (no scope, or not yet run), Repodar sends, after the Forge poll, one request to each site's public domain, and never more than one request per site per day:
HEAD https://<site domain>/with a 5 second timeout, no credentials or cookies, and the User-AgentRepodar/1.0 (+https://repodar.dev/docs/sources). Redirects are not followed: the headers of the first response are read (PHP sends them on its own redirects too). A site asked in the last 20 hours is not asked again, so "Collect now" or saving the token again sends nothing new. At most 4 requests at a time, 60 per poll and 15 seconds in total; sites not reached are asked first next time. Sites named likelocalhost,*.test,*.local,*.corp, IP addresses (or names embedding one, such as127.0.0.1.nip.io), wildcards and names without a dot are skipped.- It reads
X-Powered-By: PHP/8.5.12(or aServerheader carryingPHP/x.y.z). Only that version string is stored; nothing else from the response is kept or logged. - The header counts only when it agrees with the major.minor Forge reports for that site. If it does not (a CDN, proxy or another app answered), it is ignored.
- No header: the site shows Forge's 8.5, exactly as before. A timeout or network error keeps the exact version learned on an earlier day, as long as Forge still reports the same major.minor.
The tooltip then reads "observed on Forge site example.com · server web-1 · exact version from
example.com response headers". To get exact versions, either set expose_php = On in the PHP
settings Forge manages for that site's PHP version (the FPM php.ini on the server's PHP page) so
PHP sends X-Powered-By, or pin the exact version in the repository's .php-version. Leaving
expose_php off is a reasonable security choice; Repodar then shows the major.minor.
Server reports
For servers that are not on Forge (AWS, DigitalOcean droplets, bare metal), or to report from the server itself, paste one line. The script finds your sites and the PHP that serves each one, and needs no editing:
REPODAR_TOKEN=rdr_… sh -c "$(curl -fsS -m 10 https://repodar.dev/report.sh)" || true
Where: as a daily cron job (or any scheduler), once per server: with no other context it reports every site on the machine in one request. The same line at the end of a deploy script reports just that site right after each deploy. Self-hosted, use your instance's
/report.sh: it already reports to yourAPP_URL.Needs: a report token from Settings > Sources > Other servers > Server reports (Generate token). A workspace can hold up to 10 active tokens; give each a name. To keep the token out of scripts, store it once and drop
REPODAR_TOKEN=from the line:mkdir -p ~/.repodar && printf '%s\n' rdr_… > ~/.repodar/token && chmod 600 ~/.repodar/tokenCheck first: add
--dry-runto print the JSON instead of sending it (sh -c "$(curl …)" report --dry-run; the wordreportfills the script's$0), andREPODAR_DEBUG=1to see each step and the answer on stderr.--site example.comreports that one site.It never fails a deploy or a cron job: every path ends with exit code 0, every request has a 10 second limit (
curl -m 10), and errors are printed only withREPODAR_DEBUG=1. Without a token it prints one line to stderr and stops.
What the script reads and sends
It needs sh, sed, head, tr and curl 7.55 or newer (for -H @-). It reads, and nothing
else:
$FORGE_SITE_PATHand$FORGE_PHPwhen a Forge deploy script runs it, else the current directory when it is/home/<user>/<domain>/…, else every site: each directory/home/*/<name>whose name contains a dot (not starting with one), plus the firstserver_nameof every server block in/etc/nginx/sites-enabled/*(skipping_,default,localhostand wildcards); at most 50 sites;- per site, the PHP that serves it:
$FORGE_PHP, else thefastcgi_pass unix:/var/run/php/phpX.Y-fpm.sock(or Forge'sphpX.Y-fpm-<site>.sock) line in/etc/nginx/sites-available/<site>orsites-enabled/<site>(or the file whoseserver_namenamed the site), elsephponPATH; thenphpX.Y -r 'echo PHP_VERSION;'(5 second timeout whentimeoutexists); andnode -vfor a site whose directory has apackage.json; - for the machine:
php,node,bun,deno,go,python3,ruby,java,dotnetandrustcfromPATH(their version output), andhostname. Node.js comes fromPATHas is: the script does not switch versions withnvmorfnm, even when a site has an.nvmrc; - the token from
$REPODAR_TOKEN, else the first line of~/.repodar/token.
It sends one request, POST /ingest/runtime with the token as Authorization: Bearer rdr_…
(passed to curl on stdin, never on its command line):
{ "host": "web-1", "agent": "report.sh/1",
"runtimes": { "php": "8.5.12", "node": "22.11.0" },
"sites": [ { "site": "archfeel.cz", "runtimes": { "php": "8.5.12" } },
{ "site": "shop.example.com", "runtimes": { "php": "8.4.11" } } ] }
Only values made of A-Z a-z 0-9 . _ - are ever put in it; a site directory or server_name with
anything else is dropped. Nothing from the network is ever run or interpreted: the answer is only
printed with REPODAR_DEBUG=1. On success it prints one line, such as
repodar: reported 2 sites (php 8.5.12, 8.4.11) from web-1.
The script is open source at
apps/web/report/report.sh
and served verbatim at /report.sh; the only difference on a self-hosted instance is the default
URL line. REPODAR_URL=https://repodar.example.com points it elsewhere (plain http:// only for
localhost or 127.0.0.1, with an optional port).
SHA-256 of the script as served by https://repodar.dev/report.sh: c4b2d1a72ed3d22ecb4d3233f0cb37d0af257ed5cc2ea1666a3f3139748aadab.
Check it before running:
curl -fsS https://repodar.dev/report.sh | shasum -a 256
What Repodar does with a report
- Shape: the batch above (≤ 50 sites, ≤ 16 KB), or the single report earlier snippets sent,
{ "site": "archfeel.cz", "host": "web-1", "repo": "owner/name", "runtimes": { "php": "8.5.12" } }. The machine-levelruntimesfill in for every site without its own value for that runtime. Each site needssiteorrepoand at least one runtime; a site that fails validation is skipped and listed in the answer, the others are kept. Runtime names:php,node,bun,deno,go,python,ruby,java,dotnet,rust. Versions are plain releases with one to three numbers; a leadingvis dropped, prereleases, distribution suffixes and all-zero versions (8.5.0RC1,8.3.6-1ubuntu1,0.0.0) are refused. Site and host must be plain ASCII names. - Stores: per site: the site, host, repository hint, runtime versions, the sending agent and the time received, as one entry per host + site + repository hint. Entries not refreshed for 30 days are deleted, and a workspace keeps at most its 200 newest. Settings list the last report of each host (sites, agent, when).
- Matching: by
repofirst (your repository with exactly thatowner/name, ignoring case), otherwise bysitethrough your Forge sites: the Forge site with that name lends its repository match. A site that leads to two different repositories, or to none, stays unmatched. - Precedence: a report is
observedand beats every other source: report, Forge command, Forge header probe, pin, CI, Forge's major.minor. PHP from a report always applies to the matched repository; any other runtime refines a runtime the repository already uses and is otherwise listed under Also on this server, without a chip. Every runtime name gets EOL checks once the repository uses it. When several hosts report one repository, the lowest version counts, since that is the one that matters for end of life and security; the tooltip names every host ("reported by web-1, web-2 for archfeel.cz 2 h ago · lowest 8.5.11"). If a fresh report disagrees with the major.minor Forge reports, the report wins and the repository page shows a note such as "Forge reports php 8.4, the server reports 8.5.12". - Freshness: a report counts for 48 hours, then the repository falls back to the next source on its next scan; it stays listed on the repository page as "last report 3 d ago".
- Rescans: a report that changes the merged result for a repository queues a rescan of just that repository, under the same daily caps as a manual Rescan. Daily reports repeating the same versions cost nothing.
Token handling and rotation
A token is rdr_ followed by 32 random bytes (base64url). It is shown once, when generated, with
the lines already filled in; Repodar stores only its SHA-256 and cannot show it again. To rotate,
generate a new token, update the servers, then Revoke the old one: from then on it gets a 401,
and the script still exits 0. Settings show when each token was last used (updated at most once an
hour). Re-submitting the Generate form, for example by reloading the page that shows a new token,
creates nothing.
Limits and answers
One request per run, 10 per token per minute and 120 per workspace per hour; the body may be at
most 16 KB. Answers are JSON: 202 with matched (site and repository), unmatched sites and,
when any were skipped, rejected (site and reason); a single report also gets repo. 400 for
a bad body, 401 for a missing, unknown or revoked token, 413 over 16 KB, 429 over a limit
(with Retry-After). Failed token checks also count against the sending IP, like bad webhook
signatures.
Cloudflare
- Needs: a custom API token with Account Settings: Read, Workers Scripts: Read and Cloudflare Pages: Read. Add Workers CI: Read so Workers deployed by Workers Builds link to their repository.
- Reads, once a day: the accounts the token sees, their Workers scripts (and Workers Builds triggers) and Pages projects.
- Sets: deploy target Cloudflare and the last deploy (a Worker's last upload, a Pages
project's newest successful deployment).
compatibility_dateand thenodejs_compatflag are shown as information only: Workers do not run a Node.js version. - Matching: Pages projects by their connected GitHub repository, Workers by their Workers
Builds repository connection. A Worker deployed with
wrangler deployfrom your laptop or CI has no repository link, so it appears as unmatched; map it by hand.
Vercel
- Needs: a Vercel access token scoped to the teams Repodar should see. Vercel tokens cannot be
limited to reading; Repodar only sends
GETrequests. - Reads, once a day: your personal projects and those of every team the token sees, and their newest ready deployment.
- Sets: deploy target Vercel, the project's Node.js version (
22.xbecomes Node 22,observed, which beatsengines.node) and the last deploy. The framework preset is shown as information only; the lockfile stays the truth for framework versions. - Matching: the project's linked GitHub repository.
DigitalOcean App Platform
- Needs: a personal access token with the custom scope
app:readonly. - Reads, once a day: your App Platform apps and their specs.
- Sets: deploy target DigitalOcean and the active deployment's time. A runtime version counts
as
observedonly when a component runs a prebuilt runtime image (php:8.3-fpm,node:22-alpine,golang:1.23); buildpacks name the runtime without a version and are shown as information. A component built from adockerfile_pathleaves the version to the repository'sDockerfile. - Matching: the GitHub repository each component builds from. An app built from several repositories yields one entry per repository.
GitHub Deployments
- Needs: the GitHub App permission Deployments: Read-only, accepted by each installation (see self-host.md). No token: it uses the same access as the scan.
- Reads, once a day: the newest ten deployments per repository and the statuses of the newest deployment per environment, at most 400 requests per run per team. Repositories beyond that are read first on the next run.
- Sets: the last successful deployment per environment, shown as "deployed 2 d ago ·
production". It is the "last deployed" value only when no platform above provides one. A
deployment created by
vercel[bot]also hints the deploy target Vercel when nothing else set one. - Matching: none needed, it reads each repository directly.
- Without the permission: the source probes one repository per installation, gets a 403 and waits; settings say "Waiting for permission" and nothing else changes.
GitHub token for URL-added repositories
Not a deploy source, but stored and checked the same way.
- Needs: a fine-grained personal access token with "Only select repositories" and Contents:
Read-only plus Metadata: Read-only. Classic tokens (
ghp_…) are refused: they carry account-wide access. - Used for: repositories you added by URL (the ones the GitHub App cannot see, usually where you are a collaborator). Without it, Repodar reads them with your sign-in token.
- Checks, once a day: which URL-added repositories the token can reach (
source:github-patin the filter).
How matching works
After every collect, each thing a provider reports (a Forge site, a Worker, a Vercel project, an app) is matched to a repository in your workspace (server reports match on arrival, see above):
- A decision you made by hand (mapped to a repository, or ignored) sticks across collects, as long as that repository still exists in Repodar.
- Otherwise the reported repository name is compared with your repositories'
owner/name, ignoring case. A different branch than the repository's default still matches; the repository page shows a note. - Otherwise it is listed as unmatched in the source's card.
Only matched entries change a repository. They are merged into the next scan, so an observed PHP version is the one checked against endoflife.date.
Sources are collected once a day before the daily scan. Rescan and Scan all collect them first too, then scan, at most once every 5 minutes per workspace (a window shared with Collect now and Check again); inside that window they scan straight away with what was last collected. A Rescan reads GitHub Deployments only for the repository it rescans.
Mapping by hand
Open Settings > Sources. Each source card lists what it found, with a Repo in Repodar column. For an unmatched entry, pick the repository it deploys and save; pick Ignore for things you do not want to track (a fork, a staging copy). Mappings change only Repodar, never the provider. Removing a source deletes its token, everything it collected and your mappings, and the affected repositories rescan with what their files declare.
To override the deploy target itself, open the repository page and choose a target under Deploy. That override beats every source.