Skip to content

feat: work on the relay — dotfile permissions, relay CLI, JWT mapping#8

Merged
khaliqgant merged 35 commits intomainfrom
auth-split
Mar 27, 2026
Merged

feat: work on the relay — dotfile permissions, relay CLI, JWT mapping#8
khaliqgant merged 35 commits intomainfrom
auth-split

Conversation

@khaliqgant
Copy link
Copy Markdown
Member

@khaliqgant khaliqgant commented Mar 26, 2026

Summary

Foundation for combining relayauth + relayfile into a local agent sandbox where agents operate under granular, permission-checked file access using relay primitives.

  • JWT claim mapping: generate-dev-token.sh now emits workspace_id + agent_name so relayfile accepts relayauth-issued tokens
  • Dotfile permissions: .agentignore / .agentreadonly with .gitignore syntax — zero config agent sandboxing
  • Relay CLI: relay up, down, provision, shell, scan, run, doctor, mount, status
  • Zero-config mode: no relay.yaml needed — agent names discovered from dot filenames
  • Specs: full architecture spec + RFC for dotfile permissions model
  • Workflows: 3 workflows (111, 112, 113) for JWT mapping, dotfiles, and relay run
  • Trajectories: 6 decisions + 1 reflection recorded via trail

Key architecture decisions (from trail)

  1. FUSE mount for local enforcement (over kernel sandbox, MCP, LD_PRELOAD)
  2. Dotfile permissions for 80% case, relay.yaml for power users
  3. OSS (free, local) vs Cloud (paid, distributed) split with @relayauth/core bridging both
  4. Cloud/relayauth duplication identified — must import from core instead of copying

Related PRs

Test plan

  • relay up starts both services locally
  • relay provision mints scoped tokens via generate-dev-token.sh
  • relay scan shows dotfile permissions
  • Workflow 111 completed (JWT claim mapping)
  • Workflow 112 completed (dotfile parser + prereqs)
  • Workflow 113 (relay run with FUSE) — depends on relayfile FUSE merge
  • End-to-end: relay run claude in a project with .agentignore

🤖 Generated with Claude Code


Open with Devin

khaliqgant and others added 7 commits March 26, 2026 23:28
…ng, and workflows

Foundation for combining relayauth + relayfile into a local agent sandbox
where every file operation is permission-checked via relay primitives.

Core changes:
- JWT claim mapping: generate-dev-token.sh emits workspace_id + agent_name
  for relayfile compatibility
- Token types: added workspace_id and agent_name optional fields
- Test helpers: updated to include new claims and relayfile audience

Relay CLI (scripts/relay/):
- relay.sh: up, down, provision, shell, scan, run, doctor, mount, status
- dotfile-parser.ts: .agentignore/.agentreadonly with .gitignore syntax
- dotfile-compiler.ts: compiles dotfiles to relayfile ACL rules
- seed-acl.ts: seeds .relayfile.acl markers via relayfile API
- parse-config.ts: relay.yaml parser with scope validation
- Zero-config mode: no YAML needed, agent names from dot filenames

Specs & RFC:
- specs/work-on-the-relay.md: full architecture spec
- specs/rfc-dotfile-permissions.md: RFC for Will on dotfile model

Workflows:
- 111: JWT claim mapping + relay CLI (completed)
- 112: dotfile permissions + prereqs (completed)
- 113: "relay run" command with FUSE mount integration

Trajectories: 6 decisions + 1 reflection recorded via trail

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Zero-dependency core package containing token verification, scope
parsing/matching, ACL evaluation, and config helpers. Imported by
both the OSS relayauth server and the cloud repo (replacing the
duplicated source files in cloud/packages/relayauth).

Exports: verifyToken, ScopeChecker, parseScope, matchScope,
filePermissionAllows, resolveFilePermissions, parsePermissionRule.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ci.yml: build, typecheck, test on PR and push to main
- publish.yml: manual workflow dispatch to publish @relayauth/types,
  @relayauth/core, @relayauth/sdk to npm with provenance.
  Supports version bump (patch/minor/major/pre*), dry run, dist-tags.
  Auto-creates git tag on publish to match relay repo pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Matches relay repo pattern: update npm before publish (required for
provenance attestation) and --ignore-scripts for safety.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

khaliqgant and others added 2 commits March 26, 2026 23:45
Relayfile auth.go expects short scopes like "fs:read" while relayauth
uses the full format "relayfile:fs:read:/path". Include both in the
token so it works with both systems. Also fix admin token scopes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
e2e-dotfiles.sh: fix isReadonly argument order (path, perms)
e2e-dotfiles.sh: rewrite compile probe to CJS pattern (async IIFE + require)
e2e-dotfiles.sh: fix compileDotfiles args (workspace string, not perms object)
e2e-dotfiles.sh: fix property access (acl, ignoredPaths)
publish.yml: fix matrix expression to produce proper JSON array
install.sh: replace hardcoded path with dynamic resolution

Co-Authored-By: My Senior Dev <dev@myseniordev.com>
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 new potential issues.

View 18 additional findings in Devin Review.

Open in Devin Review

Comment on lines +364 to +365
assert_array_contains "$compile_json" "data.scopes" "relayfile:fs:read:*" \
"Compiler scopes include relayfile:fs:read:*"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 E2E test asserts wildcard scope that the compiler never produces

The verify_compiler_contract function asserts that data.scopes contains "relayfile:fs:read:*" (line 364). However, the compiler (dotfile-compiler.ts:38-40) produces path-specific scopes like "relayfile:fs:read:/README.md" — it never emits wildcard scopes. This assertion will always fail, causing the e2e test to report a false failure.

Prompt for agents
In scripts/relay/e2e-dotfiles.sh, line 364-365, replace the wildcard scope assertion with an assertion that checks for a specific path-based scope that the compiler actually produces. For instance, check that data.scopes contains a scope matching the pattern 'relayfile:fs:read:/README.md' (since README.md is a readable file in the test fixture created at line 274). The compiler at dotfile-compiler.ts:38-40 generates scopes in the format relayfile:fs:{action}:/{relativePath}, so the assertion should check for one of those specific scopes rather than a wildcard.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +367 to +378
if JSON_INPUT="$compile_json" node -e '
const data = JSON.parse(process.env.JSON_INPUT);
const acl = data.aclRules ?? {};
const rules = acl["/"] ?? [];
if (!Array.isArray(rules)) process.exit(1);
if (!rules.includes("deny:agent:test-agent")) process.exit(1);
if (!rules.includes("allow:scope:relayfile:fs:read:/*")) process.exit(1);
'; then
print_pass "Compiler restricts README.md writes via root ACL while preserving read"
else
print_fail "Compiler did not encode readonly root ACL as expected"
fi
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 E2E test asserts allow:scope:* ACL rule that the compiler never produces

The verify_compiler_contract function at lines 367-373 asserts that the root ACL (/) contains both "deny:agent:test-agent" and "allow:scope:relayfile:fs:read:/*". However, the compiler (dotfile-compiler.ts:76-89) only ever generates deny:agent:* rules for ignored files — it never produces allow:scope:* rules. For readonly files, it simply adds a read scope to the token and omits the write scope, rather than creating ACL allow rules. This assertion will always fail.

Prompt for agents
In scripts/relay/e2e-dotfiles.sh lines 367-378, the test expects the root ACL to contain 'allow:scope:relayfile:fs:read:/*' but the compiler in dotfile-compiler.ts never emits allow:scope rules. The compiler handles readonly files by adding a read scope to the token but NOT adding a write scope (lines 82-85), and only producing deny:agent rules for ignored files (line 78). Update this assertion to match the actual compiler behavior: either check that README.md appears in the scopes list as a read scope but NOT as a write scope, or check that the root ACL does not contain deny rules (since README.md is readonly, not ignored, the root ACL may not have any deny rules related to it).
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +388 to +397
if JSON_INPUT="$parser_json" node -e '
const data = JSON.parse(process.env.JSON_INPUT);
if (data.semantics?.ignoresSecretsDir) process.exit(1);
if (data.semantics?.ignoresDotEnv) process.exit(1);
if (data.semantics?.readonlyReadme) process.exit(1);
'; then
print_pass "Per-agent empty dot files clear global ignore/readonly rules for admin-agent"
else
print_fail "admin-agent still inherits global dot-file restrictions"
fi
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Admin-agent override test wrongly assumes empty per-agent dotfiles clear global restrictions

The verify_admin_override_contract function (lines 381-409) creates empty .admin-agent.agentignore and .admin-agent.agentreadonly files, then asserts that admin-agent has NO ignore/readonly restrictions (lines 388-393). But parseDotfiles (dotfile-parser.ts:51-73) loads global .agentignore patterns first, then layers per-agent patterns on top additively via the ignore library. Empty per-agent files add nothing and do NOT clear the global patterns. So admin-agent will still inherit secrets/ and .env from the global .agentignore and README.md from .agentreadonly. The test will always report a failure at line 396: "admin-agent still inherits global dot-file restrictions".

Prompt for agents
In scripts/relay/e2e-dotfiles.sh lines 381-409, the admin-agent override test is fundamentally incorrect. The parseDotfiles function (dotfile-parser.ts:51-73) adds global patterns first, then per-agent patterns additively. Empty per-agent files do NOT clear global patterns. Either:
1. Change the test to expect that admin-agent STILL inherits global restrictions (the correct behavior given the parser implementation), OR
2. If admin-agent override is a desired feature, update the parser in dotfile-parser.ts to support a convention like a '!' negation line or a special '# clear' directive in per-agent files that clears inherited global patterns, then adjust the test to use that convention.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

- seedAclEntries now uses POST /fs/bulk instead of PUT /fs/file
  (bulk write doesn't require If-Match for new files)
- Always seed compiled ACLs in dotfile mode (was only seeding for relay.yaml)
- ACL markers (.relayfile.acl) now created in relayfile workspace

Note: relayfile Go server doesn't yet enforce .relayfile.acl markers —
ACL eval logic exists in @relayfile/core (TypeScript) but isn't wired
into Go HTTP handlers. Separate PR needed in relayfile repo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 19 additional findings in Devin Review.

Open in Devin Review

Comment on lines +10 to +39
globalThis.fetch = (async (input: URL | RequestInfo, init?: RequestInit) => {
calls.push({ input: new URL(String(input)), init });
return new Response(null, { status: 200 });
}) as typeof fetch;

try {
await seedAclEntries(
"workspace-a",
{
"/": ["agent:root:read"],
"/src": ["agent:dev:write"],
},
"http://127.0.0.1:8080/",
"token-123",
);
} finally {
globalThis.fetch = originalFetch;
}

assert.equal(calls.length, 2);
assert.equal(calls[0]?.input.pathname, "/v1/workspaces/workspace-a/fs/file");
assert.equal(calls[0]?.input.searchParams.get("path"), "/.relayfile.acl");
assert.equal(calls[1]?.input.searchParams.get("path"), "/src/.relayfile.acl");
assert.equal((calls[0]?.init?.headers as Record<string, string>)?.authorization, "Bearer token-123");

const firstBody = JSON.parse(String(calls[0]?.init?.body));
assert.deepEqual(firstBody, {
content: JSON.stringify({ semantics: { permissions: ["agent:root:read"] } }),
encoding: "utf-8",
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 ACL unit tests completely mismatched with bulk endpoint implementation

The seedAclEntries implementation (packages/core/src/acl.ts:20) sends a single POST to /v1/workspaces/.../fs/bulk with all files, but the test at packages/core/src/__tests__/acl.test.ts was written for a per-entry API that no longer exists. The test asserts calls.length === 2 (line 29), expects pathname /fs/file (line 30), and checks per-entry body structure (line 35-39) — none of which match the bulk implementation. Additionally, the mock returns new Response(null, { status: 200 }) (line 12), but the implementation calls response.json() (packages/core/src/acl.ts:36) on the response, which will throw a JSON parse error on null body, causing the first test to error rather than even reach the assertions. The second test (line 50) expects the regex /failed to seed ACL for \/src: HTTP 500 boom/ but the implementation throws "failed to seed ACLs: HTTP 500 boom" (plural, no path).

Prompt for agents
Rewrite the tests in packages/core/src/__tests__/acl.test.ts to match the bulk endpoint implementation in packages/core/src/acl.ts. The implementation now makes a single POST to /v1/workspaces/{ws}/fs/bulk with body {files: [{path, content, encoding}, ...]}. The mock fetch should return new Response(JSON.stringify({errorCount: 0, errors: []}), {status: 200}) instead of new Response(null, {status: 200}). The first test should assert calls.length === 1, check the pathname is /v1/workspaces/workspace-a/fs/bulk, and verify the body contains a files array with both ACL entries. The second test's regex should match the actual error message 'failed to seed ACLs: HTTP 500 boom' (plural, no per-path info).
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Previously, any ignored file added deny:agent:X to its parent dir,
which blocked all files in that dir (including allowed ones). Now
deny rules only apply to directories where every file is ignored
(e.g. /secrets/, /config/). Mixed directories rely on token scopes
for per-file enforcement.

Also fixed ACL seeder to use bulk write endpoint (no If-Match needed).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 22 additional findings in Devin Review.

Open in Devin Review

Comment on lines +405 to +406
assert_array_contains "$compile_json" "data.scopes" "relayfile:fs:write:*" \
"admin-agent retains write scope"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 E2E admin-agent compile test asserts wildcard relayfile:fs:write:* scope that compiler never produces

At e2e-dotfiles.sh:405, the test asserts admin-agent scopes contain relayfile:fs:write:*. The compiler (dotfile-compiler.ts:91-92) only adds per-file write scopes like relayfile:fs:write:/src/app.ts, never wildcard relayfile:fs:write:*. This assertion will always fail.

Suggested change
assert_array_contains "$compile_json" "data.scopes" "relayfile:fs:write:*" \
"admin-agent retains write scope"
assert_array_contains "$compile_json" "data.scopes" "relayfile:fs:write:/src/app.ts" \
"admin-agent retains write scope"
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

khaliqgant and others added 14 commits March 27, 2026 10:27
…y from seed

Agent tokens now only contain per-file relayauth scopes (relayfile:fs:read:/path).
The relayfile server and mount client both understand this format.
Blanket short scopes were overriding per-file permissions.

Also exclude .relay/, .git/, node_modules/ from workspace seeding
to prevent internal files from appearing in the agent's workspace.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Since the relay IS the sandbox (permission-enforced workspace), agents
should run with full autonomy inside it. relay run now auto-applies:
- claude: --dangerously-skip-permissions
- codex: --dangerously-bypass-approvals-and-sandbox
- gemini: --yolo
- aider: --yes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
BASH_SOURCE[0] is empty in zsh when sourced. Use ZSH_EVAL_CONTEXT
and ${(%):-%x} as fallbacks for script path resolution and
sourced-vs-executed detection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…cape code pollution

zsh prompts with terminal title setting inject escape codes into pwd
output, breaking cd-based path resolution. Use dirname string
manipulation instead. Fall back to known paths if dirname fails.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ZSH_EVAL_CONTEXT contains "file" (not "*:file:*") when sourced in zsh.
Check ZSH_VERSION first since in zsh BASH_SOURCE is always empty,
causing the bash check to fall through to the wrong branch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ctive terminal)

Background subshell with wait causes "suspended (tty output)" for
interactive agents. Use foreground exec instead — agent gets the
terminal directly. Cleanup runs after agent exits.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…entity

"relay run codex" should use "default-agent" (the provisioned identity),
not "codex" as the agent name. The CLI name is which binary to run,
the agent name is which identity/token to use.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The config parser and dotfile scripts import from @relayauth/sdk which
needs dist/ built. check_prereqs now runs turbo build if dist is missing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 new potential issues.

View 10 additional findings in Devin Review.

Open in Devin Review

Comment on lines +364 to +365
assert_array_contains "$compile_json" "data.scopes" "relayfile:fs:read:*" \
"Compiler scopes include relayfile:fs:read:*"
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 E2E test asserts wildcard scope relayfile:fs:read:* but compiler only produces per-file scopes

The verify_compiler_contract function asserts that data.scopes contains relayfile:fs:read:*, but the compileDotfiles function in scripts/relay/dotfile-compiler.ts:38-41 generates per-file scopes (e.g., relayfile:fs:read:/README.md) via the addScope helper — it never produces wildcard scopes. This causes the assertion to always fail, reporting a false test failure. The same incorrect assertion is repeated at line 405 for admin-agent in verify_admin_override_contract.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +367 to +378
if JSON_INPUT="$compile_json" node -e '
const data = JSON.parse(process.env.JSON_INPUT);
const acl = data.aclRules ?? {};
const rules = acl["/"] ?? [];
if (!Array.isArray(rules)) process.exit(1);
if (!rules.includes("deny:agent:test-agent")) process.exit(1);
if (!rules.includes("allow:scope:relayfile:fs:read:/*")) process.exit(1);
'; then
print_pass "Compiler restricts README.md writes via root ACL while preserving read"
else
print_fail "Compiler did not encode readonly root ACL as expected"
fi
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 e2e-dotfiles.sh asserts root ACL contains allow:scope rule but compiler never generates allow rules

At line 367-378, the verify_compiler_contract() function checks that root ACL (/) contains both deny:agent:test-agent and allow:scope:relayfile:fs:read:/*. But the compiler at scripts/relay/dotfile-compiler.ts:99-106 only generates deny:agent:X rules for directories where ALL files are ignored. For the root directory /, .env is ignored but other files (like README.md, src/app.ts) are not, so it's a "mixed" directory and gets no ACL rules at all. The compiler never generates allow:scope:* rules anywhere. This assertion will always fail.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

khaliqgant and others added 5 commits March 27, 2026 11:31
…eedback

After initial sync, relay run generates a _PERMISSIONS.md (and CLAUDE.md)
listing exactly which files are read-only and which are hidden, so agents
understand the permission model without checking logs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Subshell PIDs in .relay/pids may not match the actual service process.
Now also kills any process on :8787 and :8080 to handle orphans.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…om previous run)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 13 additional findings in Devin Review.

Open in Devin Review

Comment on lines +94 to +95
echo "list=types,core,sdk,cli" >> "$GITHUB_OUTPUT"
echo 'matrix=["types","core","sdk","cli"]' >> "$GITHUB_OUTPUT"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Publish workflow publishes @relayauth/core before its dependency @relayauth/sdk

The publish matrix order is ["types","core","sdk","cli"] with max-parallel: 1, so packages publish sequentially as types → core → sdk → cli. However, @relayauth/core declares @relayauth/sdk as a hard dependency (packages/core/package.json:19). When core is published before sdk, any consumer installing @relayauth/core will fail to resolve @relayauth/sdk because it doesn't exist on npm yet. On the very first publish this is guaranteed to fail since no prior version of @relayauth/sdk exists. The correct order should be types,sdk,core,cli.

Suggested change
echo "list=types,core,sdk,cli" >> "$GITHUB_OUTPUT"
echo 'matrix=["types","core","sdk","cli"]' >> "$GITHUB_OUTPUT"
echo "list=types,sdk,core,cli" >> "$GITHUB_OUTPUT"
echo 'matrix=["types","sdk","core","cli"]' >> "$GITHUB_OUTPUT"
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

khaliqgant and others added 3 commits March 27, 2026 12:20
token-verify.ts: configurable clockSkewLeewaySeconds, consistent #private members
auth.ts: documented intentional JWT duplication with TODO
config.ts: preserve bare "*" wildcard for scope-matcher fast-path consistency
file-acl.ts: console.warn on default-open ACL fallthrough paths
relay.sh: fix shell injection in config_agent_json, warn on default secrets
errors.ts: explicit error name strings for minification safety
config.test.ts: 7 new tests (empty agents, missing fields, path traversal, etc.)
package.json: pin @relayauth/sdk to ^0.1.0 instead of wildcard
generate-dev-token.sh: remove duplicate workspace_id/agent_name claims
types/token.ts: @deprecated tags on workspace_id and agent_name
index.ts: remove process provenance comment

Co-Authored-By: My Senior Dev <dev@myseniordev.com>
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 18 additional findings in Devin Review.

Open in Devin Review

audience_json="${RELAYAUTH_AUDIENCE_JSON:-[\"relayauth\",\"relayfile\"]}"
token_type="${RELAYAUTH_TOKEN_TYPE:-access}"
secret="${SIGNING_KEY:-dev-secret}"
payload="{\"sub\":\"${subject}\",\"org\":\"${org}\",\"wks\":\"${workspace}\",\"scopes\":${scopes_json},\"sponsorId\":\"${sponsor}\",\"sponsorChain\":[\"${sponsor}\"],\"token_type\":\"${token_type}\",\"iss\":\"${issuer}\",\"aud\":${audience_json},\"iat\":${now},\"exp\":${exp},\"jti\":\"${jti}\"}"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 generate-dev-token.sh missing workspace_id and agent_name JWT claims despite relay.sh setting RELAYAUTH_AGENT_NAME

The relay.sh provisioning flow sets RELAYAUTH_AGENT_NAME at line 662 when calling generate-dev-token.sh, but the token script never reads this env var and never includes workspace_id or agent_name in the JWT payload (line 17). The design doc (docs/auth-changes-plan.md) explicitly specifies these fields should be added, and test-helpers.ts:127-128 correctly includes them. As a result, tokens generated by the relay CLI for actual agent provisioning are missing these claims, which relayfile's auth.go expects for agent identification. This breaks the core cross-service compatibility that this PR is designed to establish.

Prompt for agents
In scripts/generate-dev-token.sh, add an agent_name variable after the workspace variable (around line 10), reading from RELAYAUTH_AGENT_NAME env var with a default of the subject:

  agent_name="${RELAYAUTH_AGENT_NAME:-${subject}}"

Then on line 17, update the payload JSON string to include workspace_id and agent_name claims. Insert after the wks field:

  "workspace_id":"${workspace}","agent_name":"${agent_name}",

The final payload line should look like:
  payload="{\"sub\":\"${subject}\",\"org\":\"${org}\",\"wks\":\"${workspace}\",\"workspace_id\":\"${workspace}\",\"agent_name\":\"${agent_name}\",\"scopes\":${scopes_json},\"sponsorId\":\"${sponsor}\",\"sponsorChain\":[\"${sponsor}\"],\"token_type\":\"${token_type}\",\"iss\":\"${issuer}\",\"aud\":${audience_json},\"iat\":${now},\"exp\":${exp},\"jti\":\"${jti}\"}"
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@khaliqgant khaliqgant merged commit 37e2790 into main Mar 27, 2026
1 check passed
@khaliqgant khaliqgant deleted the auth-split branch March 27, 2026 11:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant