What moves unchanged
Both products use a directory with SKILL.md as the entrypoint. Executable helpers, supporting documentation, and reusable files can sit beside it in conventional subdirectories. OpenAI says Codex Skills build on the open Agent Skills standard. xAI describes the same folder model for Grok Build. The Agent Skills specification also requires the declared name to match the parent directory name and treats supporting paths as relative to the Skill root.
A conservative shared directory therefore looks like this:
markdown-internal-link-checker/
├── SKILL.md
├── references/
│ └── link-rules.md
└── scripts/
└── check_markdown_links.py
Keep the cross-host front matter small. The open specification requires name and description; Grok can infer them, but declaring both removes that ambiguity. The tested Skill starts this way:
---
name: markdown-internal-link-checker
description: Check relative links in a local Markdown file and report which target files exist or are missing. Use when a user asks to validate Markdown internal links without editing files.
---
Check one local Markdown file without editing it.
1. Read `references/link-rules.md` before checking the file.
2. Run `python3 scripts/check_markdown_links.py <markdown-file>` from this skill directory.
The archive contains the full file, including the required output marker. Its script command has two different path bases: scripts/check_markdown_links.py is relative to the Skill directory, while the Markdown argument should be an absolute path to the requested file. Our exported Grok transcript showed attempts without a Skill-directory change before a final command changed into it and succeeded. A bare README.md would point to the wrong file when run from the Skill directory. The archive's README gives the working command.
Discovery paths are different
The documented shared user location is ~/.agents/skills/<skill-name>/: both Codex and Grok Build list it. We did not test that shared location; our Grok run used ~/.grok/skills/.
For a repository-local setup, Codex documents .agents/skills/ and Grok documents .grok/skills/. Unzip the fixture, go to its root, then copy the entire Skill directory into the sample project. The diff checks cover SKILL.md, the reference, and the script—not just the front matter:
install -d sample-project/.agents/skills sample-project/.grok/skills
cp -R agent-skills/markdown-internal-link-checker sample-project/.agents/skills/
cp -R agent-skills/markdown-internal-link-checker sample-project/.grok/skills/
diff -qr agent-skills/markdown-internal-link-checker \
sample-project/.agents/skills/markdown-internal-link-checker
diff -qr agent-skills/markdown-internal-link-checker \
sample-project/.grok/skills/markdown-internal-link-checker
Both comparisons should exit 0 without printing a difference. Start each host from sample-project/ and pass the absolute path of its README.md. We observed Codex's repository discovery and Grok's user-directory discovery; the Grok repository path here follows its documentation, not a completed local run. If a Grok project is untrusted, inspect its reported Skill source before treating a missing Skill as a format failure.
Do not rename either copied directory while leaving name unchanged. Our first Grok probe used a temporary directory named markdown-internal-link-checker-cross-agent-test-20260923 around a file declaring name: markdown-internal-link-checker. Grok warned that the declared name did not match the path, and that explicit run ended without the required result. Renaming the directory to exactly markdown-internal-link-checker removed the warning and the next run completed.
The same trigger uses different controls
Codex documents explicit invocation with $markdown-internal-link-checker; Grok documents a user-invocable Skill as /markdown-internal-link-checker. Both can also select a Skill from the request when its description matches, so a portable description must name the job and its boundary instead of using a broad label such as "helps with Markdown."
| Concern | Codex | Grok Build | Portable choice |
|---|---|---|---|
| Required metadata | name and description | Understands both; can infer them when omitted | Declare both explicitly and keep the directory name equal to name |
| Explicit invocation | $skill-name | /skill-name | Document both commands outside the core workflow |
| Automatic invocation | Matches description; agents/openai.yaml can disable implicit use | Matches description; when-to-use adds trigger guidance and disable-model-invocation can make a Skill slash-command only | Test a relevant and an unrelated request in fresh sessions |
| Path filtering | No equivalent is documented in the Codex Skill page | paths can hide a Skill until a matching file is touched | Leave paths out of the shared core unless Grok needs a separate adapter |
| UI metadata | agents/openai.yaml can set appearance, dependencies, and invocation policy | Grok documents argument-hint and user-invocable | Keep host UI files and trigger extensions outside the portable contract |
The open specification also defines license, compatibility, and string metadata. Grok's documentation lists model and effort as accepted but not applied; do not use them to promise a model choice in both hosts. Our test used python3 already installed on one Mac. It says nothing about Windows, Linux, dependency installation, or another Python version.
allowed-tools is not a portable permission boundary
The Agent Skills specification labels allowed-tools experimental. xAI's Skill documentation says Grok accepts the field but does not use it to grant or restrict tools. We could not run a fresh Grok permission probe because the account reached its 24-hour CLI allowance. That is a documented behavior claim, not a Grok runtime result from this test.
We did run the archive's separate canary Skill in Codex with allowed-tools: Read and a workspace-write sandbox. Its execution log shows /bin/bash creating skill-permission-canary.txt with exit code 0; the file contained canary created. For this Codex version and configuration, the front-matter field did not stop the Bash write. A separate read-only attempt produced no write tool call, so it cannot be counted as a host-denial result.
Set authority in the host. Codex separates sandbox mode from approval policy; its security documentation describes read-only execution and separate network controls. Grok Build uses permission modes plus allow and deny rules; xAI says a deny rule wins over an allow rule. Test the denied effect against the tool log and file state. A final answer claiming a denial is insufficient if no write call was attempted.
A verification sequence for portability failures
- Check the unhosted result. From the ZIP root, run
python3 agent-skills/markdown-internal-link-checker/scripts/check_markdown_links.py sample-project/README.md. Expect three checked targets andmissing_count: 1. This isolates script behavior from Skill discovery. - Confirm discovery. After copying the entire directory, start each host in
sample-project/. Inspect Codex's Skill list and usegrok inspect --jsonto check Grok's reported name and source path. The Grok CLI reference documents inspection and session export. - Run explicit and implicit requests separately. Use
$markdown-internal-link-checkerin Codex and/markdown-internal-link-checkerin Grok, then ask for the same link check in fresh sessions without either name. Supply the absolute path tosample-project/README.md. Check the trace for the Skill file, reference read, script call, and the exactskill_loaded: markdown-internal-link-checker-v1marker. - Test an unrelated request. In another fresh session ask,
What is the first heading in README.md? Answer with that heading only.The relevant observation is whether the trace loaded the Skill, not whether the final heading is correct. In our Codex control run, it did not load; we did not complete this control in Grok. - Probe permissions separately. The ZIP's optional canary Skill is only for a disposable project. Give it an explicit invocation under the host policy you mean to test. Count a denial only if the trace contains the attempted write and a denial, and the canary file is absent. A skipped tool call is
NOT_RUN, not a pass.
Observed result from the Markdown fixture
The test file contained an existing link to docs/setup.md, a missing link to docs/missing.md, a fragment link to the existing setup file, and external, email, and same-page links that the reference told the checker to ignore. The checker verifies the target file, not whether #requirements names a real heading. Both hosts returned this script result:
{
"source": "README.md",
"checked": [
{"target": "docs/setup.md", "status": "exists"},
{"target": "docs/missing.md", "status": "missing"},
{"target": "docs/setup.md#requirements", "status": "exists"}
],
"missing_count": 1
}
| Check | Codex 0.155.0-alpha.9.2 | Grok 1.0.41 |
|---|---|---|
| Discovered the Skill | Yes, from repository .agents/skills | Yes, from temporary user ~/.grok/skills |
| Explicit invocation | Returned the required JSON and marker | Returned the required JSON and marker after the directory-name repair |
| Implicit invocation | Selected the Skill from the plain link-check request | Selected the Skill from the same request |
| Reference read | Visible in the Codex execution log | Visible in the exported Grok transcript |
| Script execution | Python checker ran in the Skill directory | Python checker ran after the command changed into the Skill directory |
| Observed output | Three checked targets, one missing | Three checked targets, one missing |
| Unrelated heading request | No Skill load in one fresh-session trace | Not run; later CLI requests hit the account allowance |
allowed-tools: Read canary | Bash wrote the canary under workspace-write policy | Not run; documented field behavior only |
This supports a narrow decision: keep one standard Skill directory for this read-only, self-contained workflow, then configure discovery and permissions per host. It does not prove that the same Skill will auto-trigger reliably, that Grok's project-local path works in every trust state, or that either host enforces a front-matter permission hint. If a host needs different scripts or instructions, test those variants separately rather than claiming the files remain identical.
