Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions elixir/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,10 @@ Notes:
- For `tracker.kind: github`, set `tracker.project_slug` to `owner/repo`.
- The GitHub tracker implementation uses GitHub Issues plus workflow labels for Symphony states; GitHub Projects v2 is not currently used as the tracker surface.
- Safer Codex defaults are used when policy fields are omitted:
- `codex.approval_policy` defaults to `{"reject":{"sandbox_approval":true,"rules":true,"mcp_elicitations":true}}`
- `codex.approval_policy` defaults to `never`
- `codex.thread_sandbox` defaults to `workspace-write`
- `codex.turn_sandbox_policy` defaults to a `workspaceWrite` policy rooted at the current issue workspace
- Supported `codex.approval_policy` values depend on the targeted Codex app-server version. In the current local Codex schema, string values include `untrusted`, `on-failure`, `on-request`, and `never`, and object-form `reject` is also supported.
- Supported `codex.approval_policy` values depend on the targeted Codex app-server version. In the current local Codex schema, string values include `untrusted`, `on-failure`, `on-request`, `never`, and `granular`.
- Supported `codex.thread_sandbox` values: `read-only`, `workspace-write`, `danger-full-access`.
- When `codex.turn_sandbox_policy` is set explicitly, Symphony passes the map through to Codex
unchanged. Compatibility then depends on the targeted Codex app-server version rather than local
Expand All @@ -212,7 +212,7 @@ Notes:
- If a hook needs `mise exec` inside a freshly cloned workspace, trust the repo config and fetch
the project dependencies in `hooks.after_create` before invoking `mise` later from other hooks.
- `tracker.api_key` reads from `LINEAR_API_KEY` when unset or when value is `$LINEAR_API_KEY`.
- `tracker.api_key` also reads from `GITLAB_API_TOKEN` for `gitlab` and `GITHUB_TOKEN` for `github` when unset or expressed as the matching `$ENV_VAR`.
- `tracker.api_key` also reads from `GITLAB_API_TOKEN` for `gitlab` and `GITHUB_TOKEN` (or `GH_TOKEN`) for `github` when unset or expressed as the matching `$ENV_VAR`.
- For path values, `~` is expanded to the home directory.
- For env-backed path values, use `$VAR`. `workspace.root` resolves `$VAR` before path handling,
while `codex.command` stays a shell command string and any `$VAR` expansion there happens in the
Expand Down
22 changes: 10 additions & 12 deletions elixir/lib/symphony_elixir/config/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -160,15 +160,7 @@ defmodule SymphonyElixir.Config.Schema do
embedded_schema do
field(:command, :string, default: "codex app-server")

field(:approval_policy, StringOrMap,
default: %{
"reject" => %{
"sandbox_approval" => true,
"rules" => true,
"mcp_elicitations" => true
}
}
)
field(:approval_policy, StringOrMap, default: "never")

field(:thread_sandbox, :string, default: "workspace-write")
field(:turn_sandbox_policy, :map)
Expand Down Expand Up @@ -425,7 +417,7 @@ defmodule SymphonyElixir.Config.Schema do
defp tracker_env_value(provider, callback)
when is_atom(provider) and not is_nil(provider) and is_atom(callback) do
case apply(provider, callback, []) do
env_name when is_binary(env_name) -> System.get_env(env_name)
env_name when is_binary(env_name) -> resolve_env_var(env_name)
_ -> nil
end
end
Expand Down Expand Up @@ -484,7 +476,7 @@ defmodule SymphonyElixir.Config.Schema do
defp resolve_env_value(value, fallback) when is_binary(value) do
case env_reference_name(value) do
{:ok, env_name} ->
case System.get_env(env_name) do
case resolve_env_var(env_name) do
nil -> fallback
"" -> nil
env_value -> env_value
Expand Down Expand Up @@ -513,12 +505,18 @@ defmodule SymphonyElixir.Config.Schema do
defp env_reference_name(_value), do: :error

defp resolve_env_token(env_name) do
case System.get_env(env_name) do
case resolve_env_var(env_name) do
nil -> :missing
env_value -> env_value
end
end

defp resolve_env_var("GITHUB_TOKEN") do
System.get_env("GITHUB_TOKEN") || System.get_env("GH_TOKEN")
end

defp resolve_env_var(env_name), do: System.get_env(env_name)
Comment thread
coderabbitai[bot] marked this conversation as resolved.

defp normalize_secret_value(value) when is_binary(value) do
if value == "", do: nil, else: value
end
Expand Down
66 changes: 51 additions & 15 deletions elixir/test/symphony_elixir/core_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,53 @@ defmodule SymphonyElixir.CoreTest do
assert :ok = Config.validate!()
end

test "github api token falls back to GH_TOKEN when GITHUB_TOKEN is missing" do
previous_github_api_key = System.get_env("GITHUB_TOKEN")
previous_gh_token = System.get_env("GH_TOKEN")
env_api_key = "test-gh-token"

on_exit(fn ->
restore_env("GITHUB_TOKEN", previous_github_api_key)
restore_env("GH_TOKEN", previous_gh_token)
end)

System.delete_env("GITHUB_TOKEN")
System.put_env("GH_TOKEN", env_api_key)

write_workflow_file!(Workflow.workflow_file_path(),
tracker_kind: "github",
tracker_api_token: nil,
tracker_endpoint: nil,
tracker_project_slug: "owner/repo",
codex_command: "/bin/sh app-server"
)

assert Config.settings!().tracker.api_key == env_api_key
end

test "github api token prefers GITHUB_TOKEN over GH_TOKEN when both are set" do
previous_github_api_key = System.get_env("GITHUB_TOKEN")
previous_gh_token = System.get_env("GH_TOKEN")

on_exit(fn ->
restore_env("GITHUB_TOKEN", previous_github_api_key)
restore_env("GH_TOKEN", previous_gh_token)
end)

System.put_env("GITHUB_TOKEN", "preferred-github-token")
System.put_env("GH_TOKEN", "fallback-gh-token")

write_workflow_file!(Workflow.workflow_file_path(),
tracker_kind: "github",
tracker_api_token: nil,
tracker_endpoint: nil,
tracker_project_slug: "owner/repo",
codex_command: "/bin/sh app-server"
)

assert Config.settings!().tracker.api_key == "preferred-github-token"
end

test "workflow file path defaults to WORKFLOW.md in the current working directory when app env is unset" do
original_workflow_path = Workflow.workflow_file_path()

Expand Down Expand Up @@ -1599,7 +1646,8 @@ defmodule SymphonyElixir.CoreTest do

write_workflow_file!(Workflow.workflow_file_path(),
workspace_root: workspace_root,
codex_command: "#{codex_binary} app-server"
codex_command: "#{codex_binary} app-server",
codex_approval_policy: nil
)

issue = %Issue{
Expand Down Expand Up @@ -1630,13 +1678,7 @@ defmodule SymphonyElixir.CoreTest do
|> String.trim_leading("JSON:")
|> Jason.decode!()
|> then(fn payload ->
expected_approval_policy = %{
"reject" => %{
"sandbox_approval" => true,
"rules" => true,
"mcp_elicitations" => true
}
}
expected_approval_policy = "never"

payload["method"] == "thread/start" &&
get_in(payload, ["params", "approvalPolicy"]) == expected_approval_policy &&
Expand All @@ -1663,13 +1705,7 @@ defmodule SymphonyElixir.CoreTest do
|> String.trim_leading("JSON:")
|> Jason.decode!()
|> then(fn payload ->
expected_approval_policy = %{
"reject" => %{
"sandbox_approval" => true,
"rules" => true,
"mcp_elicitations" => true
}
}
expected_approval_policy = "never"

payload["method"] == "turn/start" &&
get_in(payload, ["params", "cwd"]) == canonical_workspace &&
Expand Down
8 changes: 1 addition & 7 deletions elixir/test/symphony_elixir/workspace_and_config_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -746,13 +746,7 @@ defmodule SymphonyElixir.WorkspaceAndConfigTest do
assert config.agent.max_concurrent_agents == 10
assert config.codex.command == "codex app-server"

assert config.codex.approval_policy == %{
"reject" => %{
"sandbox_approval" => true,
"rules" => true,
"mcp_elicitations" => true
}
}
assert config.codex.approval_policy == "never"

assert config.codex.thread_sandbox == "workspace-write"

Expand Down
Loading