Kitea — AI SEO AutopilotKitea — AI SEO Autopilot

What Kitea does to your repository

Last updated 29 September 2026

Kitea publishes to a site built from a repository by committing a Markdown file and merging a pull request. This page is the whole of it: the access it asks for, what it changes the first time, what it writes for each post, and what it will not touch.

1. The access you give it

You install Kitea's GitHub App on the repository your site is built from, and choose that repository on GitHub's own screen. There is no token to create and none to renew. GitHub lists these permissions before you agree, and Kitea asks for no others:

  • Contents — read and write. To read your site's code, and to commit a Markdown file for each post.
  • Pull requests — read and write. Every change Kitea makes arrives as a pull request, so there is always a record of it.
  • Commit statuses — read and Checks — read. To find out whether your host finished building after a merge. Without them Kitea cannot tell a failed deploy from one that has not reported yet, and it will say so rather than guess.
  • Actions — read and write. Used only if you tick "Let Kitea deploy after merging", described in section 8. Leave it off and Kitea never touches your workflows.
  • Metadata — read. Required by GitHub on every app.

You choose which repositories the installation covers, and you can change or remove it at any time from your GitHub settings — Kitea loses access the moment you do.

It does not ask for administration, secrets, or access to any repository you did not choose. An installation's permissions cannot be widened without GitHub asking you to approve the change.

2. Setting up, once

Most sites built from a repository do not read Markdown files out of a folder. Before Kitea publishes anything, it finds out whether yours does, and teaches it if it does not.

  • Kitea downloads your repository as an archive and uploads the bytes into a sealed sandbox. No credential of any kind travels with it — the sandbox can read the code and nothing else.
  • It runs your repository's own checks first, unchanged, to learn what "working" looks like here before it touches anything.
  • It writes a sample post, builds the site, and looks for that post in the output.
  • If the post is already rendering, there is nothing to do and nothing is changed.
  • If it is not, Kitea makes the smallest change that gets it rendering, runs your checks again, and only opens a pull request once the site builds and serves the post in the sandbox.
  • That pull request is then merged, and Kitea waits for your host before calling anything done.

The sample post is never committed. It exists in the sandbox to prove the change works, and it is left there.

3. What a post looks like in your repository

One Markdown file per post, in a folder you choose when you connect — content/blog unless you say otherwise:

content/blog/2026-09-27-emergency-drain-repair.md

---
title: "Emergency Drain Repair"
date: "2026-09-27"
slug: "emergency-drain-repair"
---

Body text as Markdown.

Service pages go in a second folder, content/pages by default, and carry no date — neither in the filename nor in the front matter. A date is what tells Hugo and Jekyll they are looking at dated content, and a service page that has one can end up sorted into a feed or filed under an archive year. Leaving it out also keeps the page's address from going stale.

Each post is committed on its own branch, opened as a pull request, merged, and the branch deleted. The post goes live the next time your site builds — immediately if your host builds on merge, or at your next deploy if you deploy on your own command.

4. What it never touches

Some paths are excluded from every change Kitea makes, whatever happened during a run:

  • .github/workflows — your CI is yours. Kitea reads what it reports and never edits it.
  • .git, .env and any file beginning with it, and node_modules.
  • Build caches — *.tsbuildinfo, .eslintcache, .turbo, .parcel-cache, .svelte-kit, .astro, .next/cache. Running your checks rewrites these, and a cache file in a pull request conflicts with every other pull request in the repository for no benefit.

Committed build *output* is not excluded. If your site is served from a folder your build writes and you commit, that folder is part of your site and Kitea treats it as such.

A setup change is also bounded: at most 60 files and 10 MB. A change larger than that is not a blog integration, and Kitea stops and tells you rather than opening it.

5. Your branch rules win

If a branch protection rule requires an approving review, or a status check that has not run, Kitea cannot merge its own pull request and does not try to get around it. It will not disable a rule, change a setting, or use an administrator override.

What it does instead is leave the pull request open and tell you which setting refused it, by name, on the Channels page. Merge it yourself and the post is live.

6. Taking something down

Revert the pull request. Every post Kitea publishes is one merged pull request containing one file, so GitHub's own Revert button removes it cleanly, and the reverting pull request is the record of why.

Disconnecting your repository in Kitea stops any further writing and deletes Kitea's copy of your token. It does not remove posts already published — those are yours, in your repository, in your history.

7. If something goes wrong

A setup run that cannot finish stops and reports, rather than continuing to work at a problem it is not solving. You get the reason, the stage it reached, and a link to any pull request it opened.

If Kitea merges a change and your host then reports that the build failed, or your site stops answering, Kitea puts every file it touched back as it was and tells you.

It does not undo anything in the other cases. If your host reports that it deliberately did not build — which is what a site that deploys on your own command reports — or reports nothing at all while your site keeps answering normally, the change stays merged and Kitea says plainly that it could not confirm the deploy. A site that has not rebuilt is not a site that is broken, and undoing correct work is not a safe default.

Questions, or something this page does not cover: support@kitea.ai.

8. If your site only deploys when you say so

Plenty of teams do not deploy on merge. Tests run, someone looks, and the deploy is a separate decision. Kitea's own repository works this way.

Nothing breaks: a merged post simply goes out with your next deploy, and Kitea tells you that is what happened rather than claiming the post is live.

If you would rather not wait, tick "Let Kitea deploy after merging" when you connect and Kitea will run your own deploy workflow after it merges — the same workflow a push would run, with your tests and your gate in front of it. It does not hold a deploy credential for your host and cannot ship around that gate. If your workflow fails, Kitea treats it exactly as a failed build: it puts every file it touched back as it was and tells you.

Two things it needs: Actions — read and write on the token, and a workflow with workflow_dispatch: among its triggers. If more than one of your workflows can be run that way, name the one that deploys — Kitea will not guess which of your workflows to press.

Ticking this box on a site that already builds on merge does nothing. Kitea waits to see whether your host reacts to the merge on its own, and only presses your workflow if nothing happens — so you never get two deploys from one merge, and it will not start a second run of a workflow that is already running for that commit.

9. If Kitea cannot set your site up

Kitea copies your repository into a sealed sandbox as an archive. Two things do not survive that, and Kitea stops and says so rather than building something wrong:

  • Git submodules. A theme or content directory pulled in as a submodule is not in the archive, so the site cannot be built. Vendor it into the repository, or do the setup below by hand.
  • Git LFS. Files stored in LFS arrive as pointer files, not their contents. A build that reads one reads the pointer. Move them out of LFS, or do the setup below by hand.

Kitea can also stop because your site's own build fails before it has changed anything, or because it could not find a way to make your site show a post. In every case nothing is merged and nothing on your site is changed.

What your site has to do. This is the whole contract, and it does not depend on your framework:

  • Read every .md file in the posts folder — content/blog unless you chose another when you connected.
  • Parse the YAML front matter at the top. A post always has title, date and slug; a service page has title and slug and no date.
  • Render what follows the front matter as Markdown.
  • Use slug for the URL rather than deriving one from the title. The slug is the address Kitea records and reports, and a derived one will not always agree with it.
  • Use date for ordering, and include the post in your sitemap.

Hugo, Jekyll, Astro and Eleventy already read Markdown with front matter out of a content directory — usually the folder only has to be one they look in. A site that renders posts from a database, a typed array or a CMS needs a loader adding.

A worked example, for a Next.js App Router site. The shape is the same in any framework; only the file reading differs:

// lib/posts.ts
import fs from "node:fs";
import path from "node:path";
import matter from "gray-matter";
import { marked } from "marked";

const DIR = path.join(process.cwd(), "content/blog");

export type Post = { slug: string; title: string; date: string; html: string };

export function allPosts(): Post[] {
  let files: string[] = [];
  try {
    files = fs.readdirSync(DIR);
  } catch {
    return []; // No folder yet — a normal state before the first post.
  }
  return files
    .filter((f) => f.endsWith(".md"))
    .map((f) => {
      const { data, content } = matter(fs.readFileSync(path.join(DIR, f), "utf8"));
      return {
        slug: String(data.slug),
        title: String(data.title),
        date: String(data.date),
        html: marked.parse(content) as string,
      };
    })
    .sort((a, b) => b.date.localeCompare(a.date));
}

Point your blog index at allPosts(), your post route at the one whose slug matches, and add them to your sitemap. Then press Set up again in Kitea: it checks your site in the sandbox and connects once it sees a post render.

Kitea is a product of Simple Optimum Solutions, Oakland, California.