Skip to content

t1k:sync-back

FieldValue
Modulet1k-kit-feedback
Version1.2.2
Efforthigh
ToolsAskUserQuestion, Bash, Edit, Glob, Grep, Read, Skill, Task, Write

Keywords: contribute, gotcha, pr, propagate, push, sync, upstream

/t1k:sync-back
[<skill-name>|<file-path>] [--dry-run|--force|--already-pushed=pr=N]

TheOneKit Sync-Back — Push Changes to Kit Repos

Section titled “TheOneKit Sync-Back — Push Changes to Kit Repos”

Push $HOME/.claude/ changes (skills, agents, rules) back to their origin kit repos as PRs. Uses GitHub MCP tools — no local clone needed.

This skill is the canonical mechanism for the kit-wide-fix discipline. See rules/kit-wide-fix-discipline.md — local-only edits regress on next t1k modules update; sync-back closes the loop by opening a kit-source PR.

/t1k:sync-back # NO ARGS = sync EVERYTHING (all unsynced files under both scan roots)
/t1k:sync-back <skill-name> # Scope to one skill, e.g. /t1k:sync-back t1k-unity-docs
/t1k:sync-back <file-path> # Scope to one file (relative to a scan root)
/t1k:sync-back <target> --dry-run # Plan + diff only, no PR
/t1k:sync-back --force # Skip confirmation. Diff is ALWAYS shown.
/t1k:sync-back <target> --already-pushed=pr=N
# Branch+PR already exist (manual or prior sync);
# skip clone/push/PR-create; verify + record telemetry only.

No-args mode = sync everything (MANDATORY default)

Section titled “No-args mode = sync everything (MANDATORY default)”

When invoked with no positional arg and no flags:

  1. Always run the full scan. Walk $HOME/.claude/ AND <cwd>/.claude/ per references/routing-and-paths.md Step 0. Do NOT short-circuit on an empty lesson queue — the queue is one input among many, never the only one.
  2. Sync every candidate found. Group by origin kit/repo per references/routing-and-paths.md Step 3 and open one PR per repo, multi-skill if needed.
  3. Never ask “what task did you mean?” A no-args invocation is an explicit instruction to sync the entire pending set. Treat it the same way git status && git add -A && git commit treats no args — process everything.
  4. If the full scan finds zero candidates, exit cleanly with:
    No pending changes to sync. Scanned: $HOME/.claude/ and <cwd>/.claude/. Lesson queue: empty. Working tree: clean vs origin.
    This is success, not a prompt for input.
  5. Anti-pattern: the sub-agent fork seeing /t1k:sync-back with no args and replying “I’ll wait for your actual task” — this happened in the May 2026 sessions. The skill MUST proceed with the full scan; clarification questions are forbidden in no-args mode.

The first non-flag token is the target. Resolution order:

  1. Skill name — if $HOME/.claude/skills/<token>/ OR <cwd>/.claude/skills/<token>/ exists, sync everything under that directory only.
  2. File path — if the token resolves to a file under a scan root (with or without the $HOME/.claude/ prefix), sync just that file.
  3. No match → HARD-FAIL: "Target '<token>' is neither a skill name nor a file under $HOME/.claude/ or $(pwd)/.claude/. Did you mean: <5 nearest skill names>?" Do NOT silently fall back to full-tree discovery — that mis-reports scope and surprises the user.

Tokens starting with - are flags, never targets.

Sub-agent context loss: sync-back invoked from a sub-agent fork loses the parent’s conversation context. The skill MUST work from arg + filesystem state alone — never assume conversation memory. No-args = scan everything; explicit-target = scope to that target. Both modes are self-contained.

Scope — Consumer Projects Only (MANDATORY)

Section titled “Scope — Consumer Projects Only (MANDATORY)”

Do NOT invoke when CWD is a kit source repo (theonekit-core, theonekit-unity, theonekit-cli, theonekit-designer, theonekit-cocos, theonekit-rn, theonekit-web, theonekit-nakama, theonekit-release-action, t1k-telemetry-worker). Those repos ARE the origin — commit directly. See references/consumer-guard.md.

Invocation Mode (MANDATORY — Background Sub-Agent)

Section titled “Invocation Mode (MANDATORY — Background Sub-Agent)”

This skill MUST run as a background sub-agent via the Task tool, NEVER inline. Exception: explicit user request (“sync this now”) → run inline so the diff is visible.

See references/sub-agent-invocation.md for the required Task call template, required fields (kitVersion, moduleVersion, cliVersion, platform, rationale), and the auto-lesson writeback protocol (submitted: true, prUrl, fingerprint).

Transport: GitHub MCP first, gh CLI fallback (sandbox-asymmetry-safe)

Section titled “Transport: GitHub MCP first, gh CLI fallback (sandbox-asymmetry-safe)”

Parent context having access to GitHub MCP or gh does NOT guarantee the spawned sub-agent has them — sandbox/permission scopes are evaluated per Task invocation. Probe BOTH at sub-agent start:

  1. Preferred — GitHub MCP: if mcp__github__* tools are listed in the sub-agent’s tool roster, use them (see references/open-pr.md main flow).
  2. Fallback — gh CLI (when GitHub MCP is absent): gh auth status must return 0 AND the sub-agent’s Bash permissions must allow gh api, gh pr create, and either gh repo clone or raw git against https://github.com/. If those pass, the sub-agent MAY proceed using gh — fetch remote content with gh api repos/{owner}/{repo}/contents/{path}, edit locally, push via git push to a working branch, then gh pr create. The PR body, staleness check, and writeback contract are UNCHANGED — only the transport differs.
  3. Neither available (MCP missing AND gh CLI blocked in child sandbox even though parent has it): DO NOT silently fail. Refuse the sync, report submitted: false with reason transport-unavailable-in-child-sandbox, and ask the parent to either (a) re-run inline (parent context has the missing tools), or (b) install GitHub MCP via claude mcp add github and retry.

This asymmetry is a real failure mode. Detecting it explicitly avoids burning the circuit-breaker failures counter on entries that would succeed if the parent ran them inline.

Decision tree — which reference do I load?

Section titled “Decision tree — which reference do I load?”

Load only the reference you need (each is self-contained):

IntentLoad
Check if CWD is a consumer project (first step, always)references/consumer-guard.md
Sub-agent invocation template + auto-lesson writeback contractreferences/sub-agent-invocation.md
Pre-flight checks (MCP, repo access, staleness, missing-target structural-stall guard)references/preflight-checks.md
Cross-ref health pre-check — don’t inherit red CI from a pre-existing-broken mainreferences/crossref-health-precheck.md
Mandatory pre-triage review BEFORE opening any PRreferences/pre-triage-review.md
Ctx-budget headroom check — diff touches any .claude/rules/*.mdreferences/ctx-budget-headroom.md
Resolve file origin + compute target path in kit reporeferences/routing-and-paths.md
Open the PR (branch → push → create, fork flow, PR body template)references/open-pr.md
Writeback submitted: true to the lesson queuereferences/queue-writeback.md
Error table, cross-platform notes, what gets synced / excludedreferences/error-handling.md

Gate tier — scale the ceremony to the diff

Section titled “Gate tier — scale the ceremony to the diff”

Resolve the tier ONCE, from the captured payload, before loading any conditional reference. A one-line diff must not pay the ceremony of a 20-file multi-kit sync.

Diff shapeTier
One kit, ≤3 files, no .claude/rules/*.md, no skill/agent create or rename, no module.jsonfast
Anything else — multi-kit, >3 files, touches rules/*.md, creates/renames a skill or agent, touches module.jsonfull

Fast tier skips: references/ctx-budget-headroom.md (n-a — no rules/*.md in the diff), t1k-skill-creator validate when the diff touches no SKILL.md / references/*.md, and the naming-prefix + modules-registry gates (n-a by construction). Steps 3–4 of references/pre-triage-review.md collapse to one line each: generic: <1 sentence> and adversarial: n/a — trivial diff.

Fast tier never skips: Pre-flight Step 0 in full, and the secret / absolute-path / empty-file scans. In particular the main cross-ref pre-check is NOT tiered — it validates the target kit’s main tree, not your diff, so a one-line PR inherits a red main exactly like a large one; it is one sub-second script run, not agent reasoning.

Record the resolved tier in the PR body: **Gate tier:** {fast | full}.

Every sync-back PR MUST contain a ## Pre-Triage Review block in the body — risk classification, gate pre-check results, generic-rationale verification, adversarial self-review, recommended triage disposition. This shifts review cost from triage (where it’s repeated per run) to filing (where it happens once). Triage’s t1k-code-reviewer just verifies the block.

Procedure + body template + failure modes (refuse-to-PR cases): references/pre-triage-review.md. The block sits AFTER ## Changed files in references/open-pr.md’s PR body template.

Pre-flight Step 0 — Kit-Freshness Guard (MANDATORY)

Section titled “Pre-flight Step 0 — Kit-Freshness Guard (MANDATORY)”

Before ANY other sync-back step (scan, routing, pre-triage, branch creation), the sub-agent MUST run these IN ORDER — the payload is captured before anything can overwrite it:

  1. Capture the payload FIRST. Read every file in the sync-back diff and hold its content (copy to a temp dir) BEFORE running any update. t1k modules update extracts the release ZIP without a per-file content diff, so a local file that has diverged ahead of the kit is silently reverted by it — including the very edit you are here to sync. See rules/prefer-local-over-global-edits.md § “Gotcha — Updates CAN clobber divergent local edits”.

  2. Skip the refresh when the payload is ahead of the kit. If a captured file differs from the kit-shipped content it was installed from, do NOT update: sync-back FIRST, let the kit re-release, then update (rules/prefer-local-over-global-edits.md § “Gotcha — Updates CAN clobber divergent local edits”, #367 EXCEPTION). Record kit-freshness: skipped (local ahead) in the PR body and jump to step 4.

  3. Otherwise refresh local kits — NON-INTERACTIVELY. Run t1k modules update --yes to pull every installed kit to its latest published tag — on the nothing-pending path this itself prints All modules are up to date. (that string is t1k modules update’s own confirmation, not t1k --version’s). Then run t1k --version as a SECOND confirmation: it prints one badge PER installed kit (<kit>@<tag> ... ✓ up to date or ⚠ update available), never a single global “all clear” line, and local-scope and global-scope installs report separately and can disagree — confirm every kit badge reads ✓ up to date rather than looking for a matching string. The --yes (-y) flag is required, not optional: without it the command ends at an Apply N update(s)? confirm prompt that a background sub-agent can never answer — with stdin closed it exits in ~1s having applied nothing (a silent no-op indistinguishable from “already current”), and with stdin held open it stalls until the Bash tool timeout.

  4. Check GitHub HEAD for unreleased commits — PER FILE. For EACH file in this sync-back diff, run a per-file commit query against the target kit repo:

    Terminal window
    gh api 'repos/The1Studio/<target-kit>/commits?path=<file>&per_page=5' \
    --jq '.[] | {sha: .sha[0:8], date: .commit.author.date, msg: (.commit.message | split("\n")[0])}'

    The ?path= filter is mandatory — the global top-5 view drops per-file commits when the kit is active and unrelated commits push them off the top, producing a silent false-clear. The ?path= value MUST be the kit repo’s real nested path (.claude/modules/<module>/skills/<name>/...), never the consumer-flattened one (.claude/skills/<name>/...) — ?path= returns [] for BOTH “genuinely no commits” and “wrong path”, so a flattened-path guess produces a silent false-clear, and for an “already exists upstream?” check reads as “no upstream home” (duplicate-skill risk). Ground-truth the real path first with git -C <target-kit-clone> ls-tree -r origin/main --name-only | grep -i '<name>', THEN query ?path= with it; if no clone is handy, clone shallow (--depth 1) — git ls-remote cannot substitute (it lists refs, not paths). If the top result for any path is newer than the SHA your local file is based on, READ that commit’s diff via gh api repos/.../commits/<sha> and either rebase local edits onto kit HEAD (clone the kit, cherry-pick) BEFORE opening the PR, or abort the sync-back. Never silently push a stale-base branch.

    Multi-file diff loop example:

    Terminal window
    for f in $(git diff --name-only main); do
    echo "=== $f ==="
    gh api "repos/The1Studio/<target-kit>/commits?path=$f&per_page=5" \
    --jq '.[0] | {sha: .sha[0:8], date: .commit.author.date, msg: (.commit.message | split("\n")[0])}'
    done

    ⚠️ Gotcha — the naive 3-way merge-base silently drops corrections that predate your reconciliation commit. When step 4 finds upstream has moved and step 4 tells you to “rebase local edits onto kit HEAD,” do NOT default to git merge-file <upstream-current> <base> <local-current> with base = (your local fix commit)^. If the local content was previously clobbered by a bad t1k modules update and then restored (rules/prefer-local-over-global-edits.md § “Gotcha”), the restoring commit’s parent is the CLOBBERED state — but any correction that predates the clobber (i.e. was already correct in a still-earlier commit, survived the clobber invisibly because the restore just brought it back) is already present in that parent, so the 3-way diff between base and local-current sees no change there and silently drops it when merged onto upstream. No conflict, no error — the content is just gone.

    Detection step (run before every sync-back commit, not optional): for each file, diff the merge OUTPUT against local-current (theirs) directly. The expected shape is “differs from theirs by nothing except the version: frontmatter line” (upstream’s version number is usually the only thing legitimately newer). If a file’s merge output instead comes back with zero diff from mine (upstream-current) — i.e. the merge changed nothing at all — that is the tell: the local correction predates your chosen base and was eaten. Fix by rebuilding that file’s result as local-current verbatim with only the version: line swapped to upstream’s, rather than trusting the 3-way diff.

    Real instance: theonekit-unity#499 (2026-08-23, DOTS-AI). The first merge pass used (reconciliation commit)^ as base for 15 files; it silently discarded the fix in 9 of them, including both worked <example> blocks on t1k-unity-dots-core-compile-doctor.md’s description: field (present since an earlier commit, invisible to the chosen base→local diff) and the BossWindUp → BossWindUpSystem/WindUpVisualCueRequest rename in t1k-unity-dots-combat-boss/SKILL.md. Caught only by running the per-file diff against theirs described above — every eaten file showed 0 diff from mine.

  5. Cross-ref health check against main (MANDATORY). Before pushing the branch, run check-skill-cross-refs.cjs against the target kit’s current main tree (in the kit clone you opened for the PR). If main already has broken skill cross-refs, every new sync-back PR inherits that red gate. Pre-existing breakage → either bundle the fix in this PR or refuse + surface (submitted: false, error: "main-crossref-broken: <refs>"). NEVER open a PR you know will land red on a pre-existing-broken main. Full procedure + decision table + PR-body record: references/crossref-health-precheck.md.

  6. Only after steps 1–5 pass clean, proceed to the normal sync-back flow (scan, routing, pre-triage, etc.).

  • Sync-back is a one-way push. A stale local will “regress” commits the sub-agent never saw — silently reverting other contributors’ work.
  • The refresh itself is destructive if run first. t1k modules update extracts the release ZIP without a per-file content diff, so an edit that is ahead of the kit is reverted. Steps 1–2 (capture first, skip the refresh when local is ahead) are what make the rest of this gate safe to run.
  • GitHub HEAD ≠ published HEAD. Modules ship as bundled modules-YYYYMMDD-HHMM tags on a schedule; in between, the kit repo accumulates unreleased commits. t1k modules update brings you to the latest TAG, not the latest commit on main. The step 4 HEAD check closes this gap.

Originating incidents for this gate: references/preflight-checks.md § “Why Step 0 exists”.

  • Cwd is INSIDE a kit source repo (theonekit-*) — sync-back is irrelevant; consumer-guard refuses earlier. Skip this step.
  • Same session already ran t1k modules update < 30 minutes ago AND has no reason to suspect a new release shipped since.
  • GitHub is unreachable (network failure) — step 4 may be skipped, but the sub-agent MUST tell the user “skipped GitHub HEAD check; sync-back may conflict with unreleased kit commits” and have them confirm before opening the PR.

Step 0.6 — Issue Claim Check (when sync-back is for an issue-linked change)

Section titled “Step 0.6 — Issue Claim Check (when sync-back is for an issue-linked change)”

Before opening any sync-back PR for a change that is linked to a GitHub issue (owner/repo#N), run:

Terminal window
node .claude/scripts/t1k-issue-claim.cjs check <owner/repo#N>
  • If state == "held" by another user: do NOT open a duplicate sync-back PR. Surface the holder and prNumber, report them to the caller, and stop.
  • If state == "free" or "stale" or "skip": proceed to open the PR.

When the sync-back PR IS opened: the PR itself becomes the claim. Use the acquire subcommand to self-assign the issue and get the body marker:

Terminal window
node .claude/scripts/t1k-issue-claim.cjs acquire <owner/repo#N> --pr <new-PR-number>

Include in the PR body:

  • Fixes #N (the bodyTrailer returned by acquire)
  • The markerLine returned by acquire (e.g., t1k-claim: owner/repo#N)
  • Apply the label returned as labelToApply (e.g., t1k:claim)

The sync-back PR with these three elements IS the durable claim — no separate claim step needed. Do NOT run gh issue edit or gh pr create for claiming; the script and PR body carry the claim.

This step is additive: the Kit-Freshness Guard (Step 0 above) MUST still run first.

  • Scan BOTH user-scope and project-scope. Walk $HOME/.claude/ AND <cwd>/.claude/ when collecting candidate files. Project-scope modules/skills (e.g., a wiki repo shipping its own module) are invisible if you only scan user-scope. See references/routing-and-paths.md Step 0 for the rules. Related: #168.
  • Pre-flight: requires t1k CLI v4.17.0+. The require-current-cli.cjs PreToolUse hook will block stale-CLI invocations of t1k sync-back (and other state-mutating commands) when the cached latest version is newer than the local binary. Run t1k self-update first if the gate fires; override (NOT recommended) is T1K_REQUIRE_CURRENT_CLI=0.
  • Run pre-flight checks BEFORE any file write — verify GitHub MCP is connected.
  • Staleness check is MANDATORY. Never push a stale branch silently.
  • Worktree, never checkout, in the kit clone. Steps 3 and 5 above both send you into a kit clone (to rebase a stale base, and to run check-skill-cross-refs.cjs against main). That clone is shared with other live Claude sessions: git checkout/switch moves the one shared HEAD and derails whichever session is mid-commit. Acquire the branch as a worktree instead — git -C <clone> fetch -q origin && git -C <clone> worktree add -b <branch> <path> origin/main — inspect read-only with git -C <clone> show <ref>:<path>, and git -C <clone> worktree remove <path> once the PR is open. If the branch is already checked out in another worktree, take a detached one (worktree add --detach <path> origin/<branch>) and push with git push origin HEAD:<branch>. Full procedure: the t1k-kit-developer agent’s § “Shared-Clone Discipline”.
  • Module-registry-sync: if the edit touches module.json, the sub-agent must also regenerate t1k-modules.json in the kit repo (gate #validate-modules-registry-sync).
  • Skill-rename + activation-fragment sync — when a sync-back diff includes a skill directory rename (e.g., t1k-rn-rn-base-old → t1k-rn-rn-base-new), the sub-agent must ALSO update every t1k-activation-*.json fragment whose sessionBaseline[] or mappings[].skills[] references the old slug. Bare-slug refs (old) and full-prefixed refs (t1k-rn-rn-base-old) both need to swap. The release-action prefixer’s buildSelfHealMap() will catch the rename on the next CI run, but shipping the activation update in the same PR keeps the SSOT consistent and avoids the per-PR drift safety-net gate (validate-activation-skill-resolution.cjs) firing on subsequent unrelated PRs against the kit. If unsure which fragments reference the renamed skill, grep: grep -rE '"(old-slug|t1k-...-old-slug)"' $HOME/.claude/.
  • Rules ctx-budget headroom — if the diff CREATES or MODIFIES any .claude/rules/*.md in the receiver kit, the sub-agent MUST run node .claude/scripts/sync-back-ctx-budget.cjs --rules-dir <receiver-rules-dir> --change <relpath>=<contentfile> ... BEFORE pushing the branch. Status overflow means the projected corpus would breach a hard cap enforced by validate-context-window-budget.cjs — 16 500 tokens kit-wide, or 6 000 for a rule landing under .claude/modules/<name>/rules/ (pass --scope module:<name>, #691) — invoke AskUserQuestion to choose restructure / abort / override. Full algorithm + decision options: references/ctx-budget-headroom.md. Motivating incident: PRs #264, #269, #274 each broke the cap on first push.
  • Marker-namespace sync — if the changeset adds any [t1k:<name> emission in a .cjs/.js/.ts file, the sub-agent MUST run node $HOME/.claude/scripts/sync-marker-namespaces.cjs --root <kit-root> in the local kit clone before pushing the branch. The script auto-appends stub rows for new prefixes to docs/marker-namespaces.md. After it runs, fill in the Purpose/Emitter/Consumer columns in each stub row and include the docs/marker-namespaces.md change in the same commit. This prevents the validate-marker-namespaces CI gate from blocking the PR. Skip this step only when the kit has no docs/marker-namespaces.md (non-core kits that use core’s registry).
  • PR is created; NOT automerged. End every invocation by reporting the PR URL and noting “review + merge in the kit repo.”
  • Security: never sync .env, settings.local.json, memory files, or files with secrets. Sanitize absolute paths before writing.
  • Skill-touching syncs MUST consult t1k-skill-creator. If any path in the diff matches $HOME/.claude/skills/*/SKILL.md or $HOME/.claude/skills/*/references/*.md, the sub-agent invokes t1k-skill-creator to validate Skillmark structure (frontmatter shape, line-count cap from gate 2, description cap from gate 3, decision-tree pattern) BEFORE opening the PR. Validation failures block the PR; warnings surface in the PR body so the maintainer reviewer sees them.
  • Naming-prefix gate on creates AND renames. If the diff CREATES or RENAMES a skill directory or agent file, the sub-agent MUST verify the new name conforms to the universal t1k- prefix rule (skills/t1k-skill-creator/references/architecture-rules.md § 0 / skills/t1k-agent-creator/references/architecture-rules.md § 0): core = t1k-{slug}, kit-wide = t1k-{kit}-{slug}, module-scoped = t1k-{kit}-{module}-{slug}. Frontmatter name: MUST match the directory/file basename. Non-conforming names will fail validate-skill-prefix.cjs / validate-agent-prefix.cjs at PR time — catch them here and reject the sync-back with a clear error message instead of opening a PR that will block on CI. Agent rename gap: auto-prefix-agents.cjs does NOT rewrite per-module module.json agents/routingOverlay fields, so the sync-back sub-agent must hand-update both manifests when renaming agents.
  • CI-prefix lookup BEFORE computing target path (consumer→kit name asymmetry). Skill names in the consumer-side install are CI-prefixed at release time by auto-prefix-skills.cjs — e.g., a kit-source path of $HOME/.claude/modules/cocos-base/skills/playable-async-utilities/SKILL.md ships to consumers as t1k-cocos-cocos-base-playable-async-utilities. When sync-back resolves a target path in a kit source repo, it MUST NOT reuse the prefixed consumer-side directory name verbatim. Instead, look up the unprefixed source slug via either (a) the skill’s frontmatter origin + module fields plus the kit’s .t1k-manifest.json (kit-flat) / modules/{module}/.t1k-manifest.json (modular), or (b) the GitHub Tree API: gh api repos/{owner}/{repo}/git/trees/main?recursive=1 --jq '.tree[].path | select(endswith("/SKILL.md"))' then match by basename or by reading frontmatter name:. references/routing-and-paths.md Step 2 examples currently show only the kit-flat unprefixed case — assuming the consumer name maps 1:1 to the kit path will mis-target every modular kit (theonekit-cocos, theonekit-unity, theonekit-designer). Always probe before writing.

Do NOT score the PR at create time. type=sync-back-pr earns merged-tier credit, so the worker requires the PR to be MERGED and returns 403 {"error":"author_attribution_failed","reason":"pr_not_merged"} for a just-opened one. A sync-back PR is open by definition at the moment gh pr create returns, so an immediate POST can only ever be rejected.

Credit is recorded on the merge instead, with no action needed here: the contribution-capture.cjs PostToolUse hook records the gh pr create ref deterministically, and t1k:contribution-flush retries it each session until the PR merges and the POST is accepted. Do NOT hand-append the tracking JSONL — the hook owns capture.

See $HOME/.claude/skills/t1k-contribution-score/SKILL.md for the full invocation contract (rubric, endpoint resolution, POST contract). Do NOT inline rubric or POST logic here — the SSOT lives in that skill.