AI Coding Assistant Context Assets: Migration and Governance Made Practical

    This article explains how to migrate AI coding assistant context assets, including global instructions, project rules, long-term memories, and custom skills and commands, without breaking them. It defines context assets, shows why migration is hard (format and metadata mismatch, differing scope models, broken references, leaked secrets, and silent failures), and introduces a tool-agnostic intermediate model with a five-step pipeline. It also provides runnable Python 3.11+ code covering discovery, extraction, secret redaction, declarative mapping, and idempotent installation with conflict handling and verification, plus team-level governance practices such as version control, context standards, rollback checklists, and least privilege.

    1. Beyond Account Restrictions: Why Context Is the Real Problem

    In late June 2025, several reports surfaced in developer communities about AI coding tool accounts being restricted. Some developers said that enterprise accounts and long-standing accounts started behaving abnormally after an upgrade or a switch to a new environment. According to media coverage and community posts, some teams ran into account failures during periods of heavy use. A third-party reverse-engineering analysis further claimed that some terminal-based coding tools read environment signals such as the local time zone and egress IP address at runtime, and encode risk-control decisions into the system prompt. These mechanisms and details have not been fully confirmed by the service providers. This article does not reproduce any specific detection logic and does not discuss any way to get around it.

    What matters more to developers is the context assets behind account risk. Accounts can be re-registered, and subscriptions can keep being paid, but the global instructions, project rules, and long-term memories accumulated over time in local config directories may not be portable. Losing them means losing the way you collaborate, your coding conventions, and your project knowledge. This article treats these configurations as “context assets” and outlines a low-risk migration approach.

    2. What Are Context Assets?

    When you work with a coding assistant, what you produce falls into roughly five categories of assets:

    Asset type Purpose Typical scope Migration value
    Global instructions Coding preferences, language habits, response style User Defines the baseline collaboration style
    Project rules Tech stack conventions, coding standards, commit and test requirements Project Affects code quality in a single project
    Long-term memory Project background, architecture decisions, and preferences retained across sessions Global or project Takes the longest to accumulate, so the loss is greatest
    Skills and commands Workflows and custom slash commands Global or project Capture team best practices
    Session history Past conversation content Session Large in volume and privacy-sensitive, so migration adds limited value

    These assets have one thing in common: they are expensive for humans to build up, but cheap for machines to generate. The goal of migration is not to copy text. It is to preserve metadata, scope, references, and traceability.

    3. Why Migration Is Hard

    3.1. Formats and metadata are not compatible

    Many tools describe rules in Markdown and declare scope with YAML front matter, for example:

    1. ---
    2. scope: project
    3. paths: ["src/**/*.py"]
    4. ---
    5. ## Coding standards
    6. - All public functions must have type annotations
    7. - Unit tests live next to the code under test and are named test_*.py

    If you copy only the body during migration, metadata such as scope and paths is easily lost. Long-term memories may also be stored as standalone entries. Once pasted into the target tool, they may be treated as plain text.

    3.2. Scope models differ

    Tools differ in how they support user-level, project-level, and subdirectory-level scope. Without an explicit mapping, a rule that applied only to a specific directory can end up applying to the whole project and impose unintended constraints.

    3.3. References and path bindings

    Rules often reference each other with relative paths, and skill packages may include scripts. If the directory structure changes after migration, these references can break.

    3.4. Sensitive information gets mixed in

    Configuration built up over time may contain API keys, tokens, internal domain names, or database connection strings. You must redact sensitive data before migration so that the exposure surface does not grow.

    3.5. Silent failures

    Manual copying looks like it worked. But if the file location, the way it is parsed, or the naming does not match, a rule may never actually take effect. Migration must therefore be validated with an installation check and a behavioral regression test.

    4. A Unified Intermediate Model and a Five-Step Pipeline

    Treat migration as a repeatable, auditable, and reversible code change. The core idea is to build a unified intermediate model that is independent of any specific tool, and then render it into the target format.

    1. from dataclasses import dataclass
    2. from typing import Literal
    3. Kind = Literal["instruction", "rule", "memory", "command"]
    4. Scope = Literal["global", "project"]
    5. @dataclass
    6. class ContextItem:
    7. kind: Kind
    8. scope: Scope
    9. source_path: str
    10. rel_path: str
    11. title: str = ""
    12. body: str = ""
    13. tags: list[str] | None = None
    14. source_checksum: str = ""

    Making kind, scope, tags, and checksum first-class fields, instead of burying them in the body, lets the migration logic make decisions automatically from metadata.

    The five-step pipeline looks like this:

    Step Goal Output
    Discovery Enumerate global and project-level assets Asset inventory
    Extraction Read each body and parse simple front matter Raw entries
    Normalization Unify newlines, deduplicate, and redact secrets Standard entries
    Conversion Generate target files according to the mapping Content ready to install
    Installation check Dry-run, back up, write, verify Execution manifest

    5. Runnable Example: Discovery, Extraction, and Normalization

    The following example uses only the Python 3.11+ standard library, with no third-party dependencies. It scans the user-level config directory and the current project’s config directory, and parses simplified YAML front matter, handling only the two common fields scope and paths.

    1. from pathlib import Path
    2. import hashlib
    3. import json
    4. import re
    5. import argparse
    6. GLOBAL_SOURCE = Path.home() / ".claude"
    7. PROJECT_SOURCE = Path.cwd()
    8. TARGET_GLOBAL = Path.home() / ".codex"
    9. TARGET_PROJECT = Path.cwd() / ".codex"
    10. GLOBAL_INDEX = {
    11. "instruction": "CLAUDE.md",
    12. "rule": "rules/*.md",
    13. "memory": "memory/*.md",
    14. "command": "commands/*.md",
    15. }
    16. PROJECT_INDEX = {
    17. "instruction": ".claude/CLAUDE.md",
    18. "rule": ".claude/rules/*.md",
    19. "memory": ".claude/memory/*.md",
    20. "command": ".claude/commands/*.md",
    21. }
    22. SECRET_PATTERNS = [
    23. (
    24. re.compile(
    25. r"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----.*?"
    26. r"-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----",
    27. re.S,
    28. ),
    29. "<REDACTED_PRIVATE_KEY>",
    30. ),
    31. (re.compile(r"\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b"), "<REDACTED_JWT>"),
    32. (re.compile(r"(?i)\bbearer\s+[A-Za-z0-9._~+/=-]{12,}"), "<REDACTED_BEARER>"),
    33. (re.compile(r"sk-[A-Za-z0-9_-]{20,}"), "<REDACTED_KEY>"),
    34. (re.compile(r"(?i)(api[_-]?key|secret|token)\s*[:=]\s*\S+"), r"\1=<REDACTED>"),
    35. ]
    36. def fingerprint(text: str) -> str:
    37. return hashlib.sha256(text.encode("utf-8")).hexdigest()[:16]
    38. def normalize_text(text: str) -> str:
    39. text = text.replace("\r\n", "\n").replace("\u3000", " ")
    40. for pattern, replacement in SECRET_PATTERNS:
    41. text = pattern.sub(replacement, text)
    42. return "\n".join(line.rstrip() for line in text.splitlines()).strip() + "\n"
    43. def parse_front_matter(text: str, default_scope: Scope):
    44. if not text.startswith("---"):
    45. return default_scope, [], text
    46. lines = text.splitlines()
    47. if not lines or lines[0].strip() != "---":
    48. return default_scope, [], text
    49. end = None
    50. for i in range(1, len(lines)):
    51. if lines[i].strip() == "---":
    52. end = i
    53. break
    54. if end is None:
    55. return default_scope, [], text
    56. meta = {}
    57. for line in lines[1:end]:
    58. if ":" in line:
    59. key, value = line.split(":", 1)
    60. meta[key.strip()] = value.strip()
    61. scope = meta.get("scope", default_scope)
    62. if scope not in {"global", "project"}:
    63. scope = default_scope
    64. raw_paths = meta.get("paths", "")
    65. tags = [
    66. item.strip().strip("\"'")
    67. for item in raw_paths.strip("[]").split(",")
    68. if item.strip()
    69. ]
    70. body = "\n".join(lines[end + 1 :])
    71. return scope, tags, body
    72. def discover(project_root: Path | None = None) -> list[ContextItem]:
    73. items: list[ContextItem] = []
    74. def scan(root: Path, index: dict[str, str], scope: Scope):
    75. if not root.exists():
    76. return
    77. for kind, pattern in index.items():
    78. if "*" in pattern or "?" in pattern:
    79. paths = sorted(root.glob(pattern))
    80. else:
    81. path = root / pattern
    82. paths = [path] if path.is_file() else []
    83. for path in paths:
    84. if path.is_file():
    85. items.append(
    86. ContextItem(
    87. kind=kind,
    88. scope=scope,
    89. source_path=str(path),
    90. rel_path=str(path.relative_to(root)),
    91. )
    92. )
    93. scan(GLOBAL_SOURCE, GLOBAL_INDEX, "global")
    94. if project_root is not None:
    95. scan(project_root, PROJECT_INDEX, "project")
    96. return items
    97. def extract(raw: ContextItem) -> ContextItem:
    98. text = Path(raw.source_path).read_text(encoding="utf-8", errors="ignore")
    99. scope, tags, body = parse_front_matter(text, raw.scope)
    100. body = normalize_text(body)
    101. return ContextItem(
    102. kind=raw.kind,
    103. scope=scope,
    104. source_path=raw.source_path,
    105. rel_path=raw.rel_path,
    106. title=Path(raw.rel_path).stem,
    107. body=body,
    108. tags=tags or None,
    109. source_checksum=fingerprint(body),
    110. )
    111. def normalize(items: list[ContextItem]) -> list[ContextItem]:
    112. unique = {}
    113. for item in items:
    114. unique.setdefault((item.kind, item.source_checksum), item)
    115. return list(unique.values())

    The example explicitly parses simple front matter and redacts content such as private keys, JWTs, and Bearer tokens. In production, you can extend it with more rules. For example, allowlists for internal IP addresses and internal domains help avoid flagging legitimate sample configuration by mistake.

    6. Conversion, Idempotent Installation, and Conflict Handling

    Target-directory mapping should be defined in declarative configuration rather than hardcoded. When rendering files, include a source comment so that every file stays traceable. The installation logic must use the rendered content to decide whether a write is needed. It should not simply compare the source file’s checksum with the target file’s.

    1. MAPPING = {
    2. "instruction": ("", "{stem}.md"),
    3. "rule": ("rules", "{stem}.md"),
    4. "memory": ("memory", "{slug}.md"),
    5. "command": ("prompts", "{stem}.md"),
    6. }
    7. def safe_component(name: str) -> str:
    8. value = re.sub(r"[^A-Za-z0-9_.-]+", "-", name)[:80].strip(".-")
    9. return value or "item"
    10. def build_target_path(item: ContextItem) -> Path:
    11. root = TARGET_GLOBAL if item.scope == "global" else TARGET_PROJECT
    12. folder, template = MAPPING[item.kind]
    13. stem = safe_component(Path(item.rel_path).stem)
    14. name = template.format(stem=stem, slug=safe_component(item.title or "untitled"))
    15. return root / folder / name
    16. def render(item: ContextItem) -> str:
    17. header = (
    18. f"<!-- migrated from: {item.scope}:{item.rel_path}; "
    19. f"source_checksum: {item.source_checksum} -->\n"
    20. )
    21. if item.kind == "memory" and item.title:
    22. return f"{header}# {item.title}\n\n{item.body}"
    23. return f"{header}{item.body}"
    24. def execute(
    25. items: list[ContextItem],
    26. dry_run: bool = True,
    27. allow_overwrite: bool = False,
    28. ) -> list[dict]:
    29. manifest = []
    30. for item in items:
    31. rendered = render(item)
    32. rendered_checksum = fingerprint(rendered)
    33. target = build_target_path(item)
    34. old = target.read_text(encoding="utf-8") if target.exists() else None
    35. if old is None:
    36. action = "create"
    37. elif fingerprint(old) == rendered_checksum:
    38. action = "skip"
    39. else:
    40. action = "overwrite" if allow_overwrite else "conflict"
    41. if not dry_run and action in {"create", "overwrite"}:
    42. target.parent.mkdir(parents=True, exist_ok=True)
    43. if old is not None:
    44. backup = target.with_suffix(target.suffix + ".bak")
    45. backup.write_text(old, encoding="utf-8")
    46. target.write_text(rendered, encoding="utf-8")
    47. manifest.append(
    48. {
    49. "source": f"{item.scope}:{item.rel_path}",
    50. "target": str(target),
    51. "action": action,
    52. "source_checksum": item.source_checksum,
    53. "rendered_checksum": rendered_checksum,
    54. }
    55. )
    56. return manifest
    57. if __name__ == "__main__":
    58. parser = argparse.ArgumentParser(description="AI coding assistant context asset migration example")
    59. parser.add_argument("--project", default=".", help="Project root directory")
    60. parser.add_argument("--write", action="store_true", help="Actually write; dry-run only by default")
    61. parser.add_argument("--allow-overwrite", action="store_true", help="Allow overwriting existing targets")
    62. args = parser.parse_args()
    63. project_root = Path(args.project).resolve()
    64. raw_items = discover(project_root)
    65. items = normalize([extract(item) for item in raw_items])
    66. report = execute(
    67. items,
    68. dry_run=not args.write,
    69. allow_overwrite=args.allow_overwrite,
    70. )
    71. print(json.dumps(report, ensure_ascii=False, indent=2))

    The execution plan produces four kinds of actions:

    • create: the target does not exist, so it can be created.
    • skip: the target content is identical to the rendered result, so it does not need to be written again.
    • conflict: the target already has different content. It is not overwritten by default and needs manual review.
    • overwrite: performed only when overwriting is explicitly allowed, after a .bak backup has been created.

    During verification, answer three questions:

    1. Were all target files generated?
    2. Does each file’s rendered checksum match the execution manifest?
    3. Have all conflict items been handled manually?

    Once these are confirmed, start a new session in the target tool and check that the rules actually take effect. For example, ask the tool to generate a function following the migrated standards, then check whether the type annotations, test naming, and directory structure are as expected.

    7. From Personal Assets to Team Assets

    Migration tools solve the problem of taking your assets with you. Team governance solves the problem of never losing them.

    7.1. Put context assets under version control

    Track global instructions, project rules, and memory entries in the repository, and review changes to them like code. You can run a dry-run in CI first:

    1. python ctx_sync.py --project . --write=false

    If there are conflict items, sensitive information, or path anomalies, the pipeline should exit with a non-zero exit code.

    7.2. Establish context standards

    • Keep general preferences at the global level and tech stack conventions at the project level.
    • Express rules in a structured form instead of long free-form text.
    • Keep a single source of truth for each convention.
    • Clean up outdated rules and redundant memories every quarter.

    7.3. Prepare a rollback checklist

    Set up a standard rollback checklist for each migration:

    • Backup directory: ./.ctx-backup/YYYYMMDD-HHMMSS
    • Archive format: tar.gz plus sha256sum
    • Recovery: roll back by target in the manifest using the corresponding .bak files
    • Verification: recompute the rendered checksums after recovery
    • Retention: 30 days for individuals; 90 days or more for teams, in line with audit requirements

    7.4. Apply least privilege

    • Perform redaction locally.
    • Never write credentials into context files.
    • Grant the migration tool read/write access to the config directory only.
    • After the write completes, delete temporary directories and export packages that contain backup files.

    8. FAQ

    How do I migrate?

    Run a dry-run first and review the execution manifest. Once you have confirmed that there are no conflict items or sensitive information, write with --write.

    What are the common mistakes?

    Common mistakes include copying only the body without parsing front matter, ignoring scope, lacking write permission on the target directory, skipping the idempotency check, and not verifying behavior after migration.

    How do I verify the result?

    Compare file checksums, check the target paths, confirm that the manifest has no unresolved conflicts, and run regression tests in the target tool.

    Will sensitive information leak?

    It will if redaction is incomplete. Keep credentials in a secrets manager, and store only placeholders or descriptions in context files, never real keys.

    9. Compliance Boundaries

    Context asset migration is discussed here only in terms of portability and governance. It must not be used to get around any platform’s risk controls. Under no circumstances should it be used to fake time zones, egress addresses, payment bindings, or similar information. It must not be used to share accounts, resell accounts, register accounts in bulk, or bypass a service provider’s detection mechanisms. When enterprise code and configuration data are involved, you must also confirm that data retention, cross-border transfer, and access control comply with your organization’s security policies.

    Managing context assets like code is a necessary engineering investment. It reduces dependence on any single platform and improves collaboration and reuse. Once instructions, rules, and memories have a unified model, version control, installation verification, and a rollback mechanism, you can switch tools without starting from zero.