Skip to main content

GitHub Account Cleanup: Audit, Archive & Remove Stale Repos

· 5 min read

My end of year project is a GitHub account repository-cleanup tool to provide safe, repeatable auditing and cleanup for my GitHub accounts. I also wanted to create a catalog of my active repos. This repo focuses on repository-level cleanup (archive/delete/catalog), but the same audit run can help you discover candidates for cloud-resource reclamation and CI/workflow maintenance.

When to use this project

  • Periodic account maintenance (end-of-year or scheduled audits).
  • Before publishing a portfolio or transferring repositories.
  • When you want a reproducible audit with a dry-run-first approach.

Functionality included in GitHub account cleanup

My TypeScript project cleans up my account in the following way:

  • Archive stale repositories,
  • Detect and delete empty repositories,
  • Remove forks,
  • Generate repo descriptions and topics with LLM and update repos with that info,
  • Generate a catalog of active repos for publishing.

High level architecture

1- primary entry is scripts/run-all.sh 2- workflows are optional 3- gh-cleanup calls github-rest and llm-completion 4- outputs go to generated/

The high-level architecture of the npm workspace monorepo:

  • packages/gh-cleanup — CLI commands and orchestration: categorization rules, scoring, reporting, and the runner that coordinates dry-run and apply flows.
  • packages/github-rest — GitHub REST helpers, typed endpoint wrappers, and shared network utilities.
  • packages/llm-completion — LLM/AI utilities: prompt helpers, request wrappers, retries, and response sanitization used by the describe step.
  • generated/ — Example outputs created by dry-run executions: catalog.md, active.json, descriptions.json, summaries, etc.
  • .github - Workflows and prompt files.
  • scripts - Top level script to clean up GitHub account, also used by

Prerequisites

This repo can be opened with Codespaces or locally with .devcontainer/devcontainer.json. The development container has all the developer setup for this project including Node.js. Once you have the repo open with the environment, create a GitHub token and an OpenAI key and an LLM model. Set these values in the root level .env.

  • A GitHub token in GH_TOKEN (classic PAT with repo scopes; delete_repo only required for destructive operations such as deleting a repo.)
  • OpenAI key for LLM generation of repo descriptions and topics and an LLM model. I used 4.1-mini from Azure OpenAI.
GH_TOKEN=
GH_USER=YOUR_GITHUB_USER_ACCOUNT
OPENAI_API_KEY=
OPENAI_ENDPOINT=https://RESOURCE-NAME.openai.azure.com/openai/deployments/MODEL_NAME/chat/completions?api-version=API_VERSION
OPENAI_MODEL=gpt-4.1-mini
OPENAI_TEMPERATURE=0.2

Install and build dependencies

The root package.json uses npm workspaces to control and access all the packages in ./packages. Use the root package.json scripts to install and build the tool.

npm install
npm run build

Try out the tool

One-line run examples

  • Remove forks (dry-run):

    npm run start -w gh-cleanup -- remove-forks
  • Archive stale repos older than a year (dry-run):

    npm run start -w gh-cleanup -- archive-stale-repos
  • Delete empty repos, no PRs, no commits, size is 0 KB (dry-run):

    npm run start -w gh-cleanup -- delete-empty-repos
  • Categorize repos (fetch languages + README, output Markdown):

    npm run start -w gh-cleanup -- categorize-repos --fetch --output=md --out=generated/catalog.md
  • Summary (write generated/summary.md):

    This creates the final active list of repos in a markdown table. I'll use this in my dfberry.github.io website to find projects with specific code, configuration, or CI.

    npm run start -w gh-cleanup -- summary --summary-out=generated/summary.md

Full run

Once you understand what the tool does, use the ./scripts/run-all.sh to clean up your GitHub account. I used my personal dfberry account to test while building. Now that it is complete, I'll use it on my work diberry account for Microsoft.

npm run run-all:apply

The apply means empty repos are deleted and active repos are updated for description and topics.

AI-assisted development

I use AI tools (Copilot, Ask/Plan/Agent) to speed development while keeping human oversight. I commit frequently and review AI suggestions line-by-line. Choose models and session types (local, background, cloud) deliberately to control cost and behavior. Never let AI run unattended on critical changes—use dry runs and manual tests. I scaffold the repo, add features incrementally, and document decisions so I can return later. Copilot helps with comments and diagrams, but I always review and adjust its output.

Next steps

Cleanup goes beyond removing unused repositories. When tidying a personal or org GitHub account you may also:

  • Reclaim unused cloud resources referenced by projects (e.g., old deployments, test clusters, or storage buckets).
  • Remove or archive unused repositories that are forks, abandoned, or no longer relevant.
  • Find and fix failing or stale GitHub Actions workflows (update action versions or workflow syntax) or remove workflows that are no longer useful.
  • Update CI matrices and runtimes (programming language versions, OS matrix entries) to reduce CI cost and avoid testing very-old combinations.
  • Bump pinned GitHub Action versions and dependencies to address deprecations and security fixes.

Deploy an Azure Functions app from a monorepo with a GitHub Action for Node.js

· 4 min read

Azure Functions apps can be locally deployed from Visual Studio Code using the Azure Functions extension or when you create the resource in the portal, you can configure deployment. These are straightforward when your app is the only thing in the repo but become a little more challenging in monorepos.

Single versus monorepo repositories

When you have a single function in a repo, the Azure Functions app is build and run from the root level package.json which is where hosting platforms look for those files.

- package.json
- package-lock.json
- src
- functions
- hello-world.js

In a monorepos, all these files are pushed down a level or two and there may or may not be a root-level package.json.

- package.json
- packages
- products
- package.json
- package-lock.json
- src
- functions
- product.js
- sales
- package.json
- package-lock.json
- src
- functions
- sales.js

If there is a root-level package.json, it may control developer tooling across all packages. While you can deploy the entire repo to a hosting platform and configure which package is launched, this isn't necessary and may lead to problems.

Monorepo repositories as a single source of truth

Monorepo repositories allow you to collect all source code or at least all source code for a project into a single place. This is ideal for microservices or full-stack apps. There is an extra layer of team education and repository management in order to efficiently operationalize this type of repository.

When starting the monorepo, you need to select the workspace management. I use npm workspaces but others exist. This requires a root-level package.json with the packages (source code projects) noted.

The syntax for npm workspaces allows you to select what is a package as well as what is not a package.

snippets/2024-04-07-functions-monorepo/package-workspaces.json
loading...

Azure Functions apps with Visual Studio Code

When you create a Functions app with Visual Studio Code with the Azure Functions extension you can select it to be created at the root, or in a package. As part of that creation process, a .vscode folder is created with files to help find and debug the app.

  • extensions.json: all Visual Studio Code extensions
  • launch.json: debug
  • settings.json: settings for extensions
  • tasks.json: tasks for launch.json

The settings.json includes azureFunctions.deploySubpath and azureFunctions.projectSubpath properties which tells Azure Functions where to find the source code. For a monorepo, the value of these settings may depend on the version of the extension you use.

As of March 2024, setting the exact path has worked for me, such as packages/sales/.

If you don't set the correct path for these values, the correct package may not be used with the extension or the hosting platform won't find the correct package.json to launch the Node.js Functions app.

  • During development: set the azureFunctions.projectSubpath to the single package path you are developing.
  • During deployment: set the azureFunctions.deploySubpath to the single package path so the hosting platform has the correct path to launch the app.

GitHub actions workflow file for Azure Functions monorepo app

When you create a Azure Functions app in the Azure portal and configure the deployment, the default (and not editable) workflow file is built for a monorepo where the app's package.json is at the root of the repository.

Yaml

snippets/2024-04-07-functions-monorepo/single-app-workflow.yml
loading...

This worklow sets the AZURE_FUNCTIONAPP_PACKAGE_PATH as the root of the project then pushes, pushd './${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}', into that path to build. The zip, zip release.zip ./* -r, packages up everything as the root. To use a monorepo, these need to be altered.

  1. Change the name of the workflow to indicate the package and project.

    name: Build & deploy Azure Function - sales
  2. Create a new global env parameter that sets the package location for the subdirectory source code.

    PACKAGE_PATH: 'packages/sales' 
  3. Change the Resolve Project Dependencies Using Npm to include the new environment variable.

    pushd './${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}/${{ PACKAGE_PATH }}'

    The pushd commands moves the context into that sales subdirectory.

  4. Change the Zip artifact for deployment to use pushd and popd and include the new environment variable. The popd command returns the context to the root of the project.

    Using the pushd command, change the location of the generated zip file to be in root directory.

    The result is that the zip file's file structure looks like:

    - package.json
    - src
    - functions
    - sales.js

  5. The final workflow file for a monorepo repository with an Azure functions package is:

snippets/2024-04-07-functions-monorepo/mono-app-workflow.yml
loading...

Examples of code includes for Dev.to

· 2 min read

My GitHub site is built with Docusaurus 2 and usually shows code snippets. Since I build to my GitHub site but want to repost to Dev.to and other platforms from RSS feeds, I need the code snippets to work with the minimal amount of manual intervention and corrections after I import the blog post into Dev.to.

If the code snippet is inside the blog post as text, the Dev.to rss feed import mangles the code snippet syntax, which requires manual fix up which is time-consuming and a step I have to remember.

Using @saucelabs/theme-github-codeblock

I found the saucelabs code block on the unofficial Docusaurus features.

JavaScript reference: js

client-end/src/index.js
loading...

Json reference: json

package.json
loading...

Bash reference: bash

scripts/addition.sh
loading...

Text reference: text

notes/Learning-DataLake/Neal.txt
loading...

Rust: reference: rs

src/main.rs
loading...

Yaml

examples/mono-app-workflow.yml
loading...