Skip to content
Closed
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

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

11 changes: 9 additions & 2 deletions .github/scripts/pr-labeler.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,15 @@ describe("pr-labeler workflow", () => {
it("keeps trusted default-branch checkout, concurrency cancel, and minimal permissions", () => {
assert.match(workflow, /ref:\s*\$\{\{\s*github\.event\.repository\.default_branch\s*\}\}/);
assert.match(workflow, /cancel-in-progress:\s*true/);
assert.match(workflow, /pull-requests:\s*read/);
assert.match(workflow, /issues:\s*write/);
assert.doesNotMatch(workflow, /pull-requests:\s*write/);
// The issues label endpoints are shared with pull requests: writing a label
// onto a PR number needs pull_requests=write alongside issues=write, which
// GitHub reports as `issues=write; pull_requests=write`. Pinning this to
// read made the workflow fail closed on the first PR that actually needed a
// label applied (#565), so write is the minimum here, not an escalation.
assert.match(workflow, /pull-requests:\s*write/);
// contents stays read — the labeler never pushes.
assert.match(workflow, /contents:\s*read/);
assert.doesNotMatch(workflow, /contents:\s*write/);
});
});
10 changes: 8 additions & 2 deletions .github/workflows/pr-labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,14 @@ concurrency:

permissions:
contents: read
# pulls.get only needs read; label mutations use the issues API.
pull-requests: read
# pulls.get only needs read, but the issues label endpoints are shared with
# pull requests: adding or removing a label on a PR number is rejected with
# "Resource not accessible by integration" unless the token also carries
# pull_requests=write (the API reports `issues=write; pull_requests=write`
# in x-accepted-github-permissions). Read-only here silently worked while
# every run happened to be a no-op sync, and failed on the first PR that
# actually needed a label written.
pull-requests: write
Comment on lines +20 to +27

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Files =="
git ls-files .github/workflows/pr-labeler.yml .github/scripts/pr-labeler.test.cjs

echo
echo "== Workflow excerpt =="
nl -ba .github/workflows/pr-labeler.yml | sed -n '1,120p'

echo
echo "== Test excerpt =="
nl -ba .github/scripts/pr-labeler.test.cjs | sed -n '140,210p'

echo
echo "== Search for related permission/docs references =="
rg -n "pull-requests: write|pull-requests: read|issues: write|label endpoints|Resource not accessible by integration|x-accepted-github-permissions|pr-labeler" .github -S

Repository: lidge-jun/opencodex

Length of output: 302


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Workflow excerpt =="
sed -n '1,140p' .github/workflows/pr-labeler.yml

echo
echo "== Test excerpt =="
sed -n '140,220p' .github/scripts/pr-labeler.test.cjs

Repository: lidge-jun/opencodex

Length of output: 7064


🌐 Web query:

GitHub Actions permissions issues write pull-requests write labels on pull request number Resource not accessible by integration x-accepted-github-permissions

💡 Result:

The error "Resource not accessible by integration" in GitHub Actions indicates that the GITHUB_TOKEN used by your workflow lacks the necessary permissions to perform the requested API operation [1][2][3]. To resolve this when managing labels or pull requests, ensure your workflow explicitly defines the required permissions in the YAML file [4][5][6]. ### 1. Required Permissions GitHub processes labels via the Issues API [5]. To add or manage labels, you typically need both pull-requests and issues permissions [7][5][8]: yaml permissions: contents: read pull-requests: write issues: write * pull-requests: write: Necessary for actions that modify the pull request itself [7][8]. * issues: write: Necessary specifically for creating new labels or when the action interacts with the labels resource [7][5][8]. ### 2. Common Causes * Forked Repositories: When a workflow is triggered by a pull request from a fork, the GITHUB_TOKEN is granted read-only access by default for security reasons [3][7][8]. * Solution: Switch the event trigger from pull_request to pull_request_target [7][5][8]. This runs the workflow in the context of the base repository, granting it higher permissions [7][8]. Note: Ensure your workflow is secure, as this grants access to repository secrets [8]. * Missing Explicit Permissions: Depending on your repository settings, the default GITHUB_TOKEN might be restricted. Explicitly declaring the permissions block (as shown above) overrides these defaults [4][5]. ### 3. Understanding x-accepted-github-permissions The x-accepted-github-permissions header is a diagnostic tool provided by the GitHub API [9][10]. If you are troubleshooting an API call directly (or through a custom script), this header will be present in the API response [9][11]. It explicitly lists the permissions required to successfully call that specific endpoint [9][12]. You can use this to verify exactly which scope is missing [9]. ### Summary of Best Practices * Grant Least Privilege: Only add the specific permissions needed (e.g., pull-requests: write, issues: write) rather than granting full repository read/write access [6]. * Verify Syntax: Always use the hyphenated form pull-requests in your YAML permissions block; pull_requests (with an underscore) is invalid syntax [13]. * Check Token Source: If using a custom GitHub App or PAT, verify that the token itself has been granted the necessary permissions in its configuration settings [9][10][11].

Citations:


🌐 Web query:

site:docs.github.com REST API add labels to an issue required permissions pull_requests issues GitHub Apps

💡 Result:

To add labels to an issue or pull request via the GitHub REST API (using the POST /repos/{owner}/{repo}/issues/{issue_number}/labels endpoint), a GitHub App requires write-level repository permissions [1][2]. Specifically, the GitHub App must be granted either of the following repository permissions [2]: 1. Issues: Write [2] 2. Pull requests: Write [2] If the GitHub App is granted write access for either "Issues" or "Pull requests," it will have the necessary authorization to add labels to an issue or pull request [2]. If these permissions are not present, any attempt to add labels will be silently dropped or rejected [3][2].

Citations:


🌐 Web query:

docs.github.com add labels to an issue pull request permissions issues write pull requests write

💡 Result:

To add or manage labels on GitHub issues and pull requests, the required permissions depend on the action being performed and the context (UI interaction versus automation via API/GitHub Actions). For manual operations in the GitHub web interface: - Applying or dismissing existing labels requires triage access to the repository [1]. - Creating, editing, or deleting labels requires write access to the repository [1]. For automation using GitHub Actions or the REST API: - GitHub internally treats pull requests as issues [2][3]. Consequently, API endpoints for managing labels are grouped under the Issues API [2]. - To modify labels in a GitHub Actions workflow, the GITHUB_TOKEN must have explicit write permissions [3]. It is recommended to include the following in your workflow file [3]: permissions: issues: write pull-requests: write Note that while pull-requests: write is often sufficient for applying existing labels to pull requests, adding labels that do not already exist in the repository may require issues: write permissions [3]. When defining these permissions in YAML, ensure you use a hyphen (pull-requests) rather than an underscore, as the latter is a common syntax error [4].

Citations:


🌐 Web query:

site:docs.github.com/en/rest/issues/labels "Add labels to an issue" permissions "Issues: write" "Pull requests: write"

💡 Result:

To add labels to a GitHub issue, you must have write access to the repository [1]. While specific permission levels can vary based on repository settings, the write access level generally encompasses the ability to manage issue metadata, such as labels [1]. Users with write access can utilize the GitHub REST API to add labels to an issue using the following endpoint: POST /repos/{owner}/{repo}/issues/{issue_number}/labels When using this endpoint, you can provide the labels as an array in the request body [1]. If you have write permissions, you may also replace existing labels or manage them directly through the GitHub web interface, provided your account and repository settings allow for such modifications [1].

Citations:


🌐 Web query:

site:docs.github.com/en/rest/issues/labels "required permissions" "pull requests" "issues"

💡 Result:

On GitHub, pull requests are treated as a type of issue, meaning that shared actions for both features, such as managing assignees, labels, and milestones, are handled through common endpoints [1]. To perform actions on issues and pull requests, users typically require write access to the repository [1]. Specific API requests for these features require identifying the repository owner, the repository name, and the specific issue or pull request number [1]. Information regarding specific permission levels for GitLab was not retrieved.

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "`#565`|pull_requests=write|x-accepted-github-permissions|Resource not accessible by integration|issues=write" . -S

Repository: lidge-jun/opencodex

Length of output: 157


🌐 Web query:

"POST /repos/{owner}/{repo}/issues/{issue_number}/labels" "pull-requests: write" issues write GitHub App permissions

💡 Result:

To use the GitHub REST API endpoint POST /repos/{owner}/{repo}/issues/{issue_number}/labels, a GitHub App must be granted the "Issues" repository permission with "write" access [1][2][3]. While this endpoint is used to manage labels on both issues and pull requests, the "Issues: write" permission is the mandatory requirement for this specific API call [4]. In the context of GitHub Apps, pull requests are considered a subset of issues, and the "Issues: write" permission covers label management for both [4]. Even if you are performing this operation on a pull request, "Pull requests: write" alone is insufficient; you must explicitly grant "Issues: write" [4]. Summary of requirements: - Permission name: Issues - Access level: Write - Scope: Repository If your GitHub App lacks the "Issues: write" permission, attempts to add labels via this endpoint will result in a 403 Forbidden error [4].

Citations:


🌐 Web query:

site:docs.github.com/en/rest/issues/labels "GitHub App" "write" "issues" "pull requests"

💡 Result:

To enable a GitHub App to create or manage issues and pull requests, you must configure the appropriate repository permissions in the app settings. For issues, GitHub Apps require the Issues permission, which should be set to Read and write access [1]. This allows the app to perform actions such as creating issues, managing labels, and updating issue details via the REST API [1]. For pull requests, GitHub Apps require the Pull requests permission, also set to Read and write access. Note that GitHub treats pull requests as a type of issue, so shared actions like managing labels, assignees, and milestones for pull requests are handled through the Issues endpoints of the REST API [1]. When performing these actions, you use the REST API endpoints associated with the repository. For example, to add labels to an issue or pull request, you would use the following request [1]: POST /repos/{owner}/{repo}/issues/{issue_number}/labels Ensure your GitHub App has been granted these specific permissions during installation on the target repository.

Citations:


Drop the extra PR write scope

.github/workflows/pr-labeler.yml#L20-L27 only needs pulls.get plus issues.* label calls, so pull-requests: write is broader than necessary here. Keep issues: write, downgrade pull-requests to read, and update .github/scripts/pr-labeler.test.cjs#L169-L177 to assert the read-only scope.

🧰 Tools
🪛 zizmor (1.26.1)

[error] 27-27: overly broad permissions (excessive-permissions): pull-requests: write is overly broad at the workflow level

(excessive-permissions)


[warning] 27-27: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment

(undocumented-permissions)

📍 Affects 2 files
  • .github/workflows/pr-labeler.yml#L20-L27 (this comment)
  • .github/scripts/pr-labeler.test.cjs#L169-L177
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/pr-labeler.yml around lines 20 - 27, Reduce the workflow’s
pull request permission from write to read while preserving issues: write in
.github/workflows/pr-labeler.yml (lines 20-27). Update the corresponding
permission assertion in .github/scripts/pr-labeler.test.cjs (lines 169-177) to
expect the read-only pull-requests scope.

Sources: Path instructions, Linters/SAST tools

issues: write

jobs:
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@ devlog/
.opencode/

# Local agent/session artifacts
# These are per-machine agent state (goalplans, ledgers, evidence scratch).
# They are never part of the product and must not be committed, not even with
# `git add -f` — see tests/repo-hygiene.test.ts, which fails if any path here
# becomes tracked again.
.codexclaw/
**/.codexclaw/
.omo/
**/.omo/

# Test-generated artifacts
tests/.tmp-*/
Expand Down
Binary file removed devlog/.DS_Store
Binary file not shown.
Binary file removed devlog/_plan/.DS_Store
Binary file not shown.
2 changes: 1 addition & 1 deletion gui/src/components/provider-catalog/provider-presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface CatalogPreset {
label: string;
adapter: string;
baseUrl: string;
responsesPath?: string;
defaultModel?: string;
/** "oauth": account login · "forward": ChatGPT passthrough · "key": API key · "local": local scaffold. */
auth: "oauth" | "forward" | "key" | "local";
Expand Down Expand Up @@ -67,4 +68,3 @@ export function filterPresets(presets: CatalogPreset[], query: string): CatalogP
if (!q) return presets;
return presets.filter(p => p.label.toLowerCase().includes(q) || p.id.toLowerCase().includes(q));
}

3 changes: 3 additions & 0 deletions gui/src/provider-icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const PROVIDER_ICON_ALIASES: Record<string, string> = {
alibaba: "alibaba-color.svg",
"alibaba-token-plan": "alibaba-color.svg",
"alibaba-token-plan-intl": "alibaba-color.svg",
"volcengine-ark-agent-plan": "https://portal.volccdn.com/obj/volcfe/misc/favicon.png",
"qwen-cloud": "qwen-portal-color.svg",
"vercel-ai-gateway": "vercel-ai-gateway-color.svg",
vllm: "vllm-color.svg",
Expand Down Expand Up @@ -77,6 +78,7 @@ const PROVIDER_DISPLAY_NAMES: Record<string, string> = {
alibaba: "Alibaba Coding Plan",
"alibaba-token-plan": "Alibaba Token Plan",
"alibaba-token-plan-intl": "Alibaba Token Plan (Intl)",
"volcengine-ark-agent-plan": "Volcengine Ark Agent Plan",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Localize the provider display name.

Line 81 adds a user-visible provider label directly to the source map, so it cannot be translated. Add a locale key and resolve the label through the dashboard i18n layer.

As per path instructions, “user-visible strings go through the i18n locale files rather than hardcoded text.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gui/src/provider-icons.ts` at line 81, Replace the hardcoded “Volcengine Ark
Agent Plan” value in the provider display-name map with the dashboard i18n
lookup, and add the corresponding key to the locale files using the existing
provider-label translation pattern. Keep the provider key
“volcengine-ark-agent-plan” unchanged.

Source: Path instructions

kimi: "Kimi",
"kimi-code": "Kimi",
moonshot: "Moonshot",
Expand Down Expand Up @@ -105,6 +107,7 @@ function providerIconAlias(provider: string): string | undefined {
export function providerIconSrc(provider: string, _hints?: ProviderIconHints): string | undefined {
void _hints;
const icon = providerIconAlias(provider);
if (icon?.startsWith("http://") || icon?.startsWith("https://")) return icon;
return icon ? `/provider-icons/${icon}` : undefined;
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@bitkyc08/opencodex",
"version": "2.7.41",
"version": "2.7.42",
"description": "Universal provider proxy for OpenAI Codex & Claude Code — use any LLM with Codex CLI/App/SDK and Claude Code",
"type": "module",
"main": "./bin/package-main.mjs",
Expand Down
11 changes: 7 additions & 4 deletions src/codex/catalog/provider-fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ function configuredReasoningSummarySupport(prov: OcxProviderConfig | undefined,
if (!prov) return undefined;
const explicit = modelRecordValue(prov.modelSupportsReasoningSummaries, id);
if (explicit !== undefined) return explicit;
return modelRecordValue(prov.modelReasoningSummaryDelivery, id) !== undefined ? true : undefined;
const providerRecord: Record<string, unknown> = prov as unknown as Record<string, unknown>;
const delivery = providerRecord["modelReasoningSummaryDelivery"] as Record<string, unknown> | undefined;
return modelRecordValue(delivery, id) !== undefined ? true : undefined;
}

export function applyProviderConfigHints(name: string, prov: OcxProviderConfig, model: CatalogModel, providerCap?: number): CatalogModel {
Expand Down Expand Up @@ -559,8 +561,9 @@ export async function gatherRoutedModels(
// Enriched (registry-hydrated) provider clones, keyed by name — the same view used above so
// custom rows get the same noVisionModels / inputModalities treatment as discovered rows.
const enrichedByName = new Map(activeProviders);
const customModels = (config.customModels ?? []).map(cm => {
const customModels = (config.customModels ?? []).flatMap(cm => {
const rawProvider = config.providers[cm.provider];
if (!rawProvider || rawProvider.disabled === true) return [];
const supportsReasoningSummaries = configuredReasoningSummarySupport(rawProvider, cm.modelId);
const base: CatalogModel = {
id: cm.modelId,
Expand All @@ -580,10 +583,10 @@ export async function gatherRoutedModels(
if (enrichedProvider && modelInList(enrichedProvider.noVisionModels, base.id)) {
const current = base.inputModalities ?? ["text"];
if (!current.includes("image")) {
return { ...base, inputModalities: [...current, "image"] };
return [{ ...base, inputModalities: [...current, "image"] }];
}
}
return base;
return [base];
});
// Custom rows override discovered rows that encode to the same Codex-facing slug.
const customKeys = new Set(customModels.map(c => routedSlug(c.provider, c.id)));
Expand Down
6 changes: 6 additions & 0 deletions src/providers/derive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface DerivedKeyLoginProvider {
label: string;
baseUrl: string;
adapter: string;
responsesPath?: string;
dashboardUrl: string;
models?: string[];
liveModels?: boolean;
Expand Down Expand Up @@ -52,6 +53,7 @@ export interface DerivedProviderPreset {
label: string;
adapter: string;
baseUrl: string;
responsesPath?: string;
defaultModel?: string;
auth: "oauth" | "forward" | "key" | "local";
codexAccountMode?: CodexAccountMode;
Expand Down Expand Up @@ -103,6 +105,7 @@ export function providerConfigSeed(entry: ProviderRegistryEntry): OcxProviderCon
return {
adapter: entry.adapter,
baseUrl: entry.baseUrl,
...(entry.responsesPath ? { responsesPath: entry.responsesPath } : {}),
authMode: entry.authKind === "local" ? undefined : entry.authKind,
...(entry.codexAccountMode ? { codexAccountMode: entry.codexAccountMode } : {}),
...(entry.keyOptional !== undefined ? { keyOptional: entry.keyOptional } : {}),
Expand Down Expand Up @@ -150,6 +153,7 @@ export function deriveKeyLoginMap(): Record<string, DerivedKeyLoginProvider> {
label: entry.label,
baseUrl: entry.baseUrl,
adapter: entry.adapter,
...(entry.responsesPath ? { responsesPath: entry.responsesPath } : {}),
dashboardUrl: entry.dashboardUrl,
...(entry.models ? { models: [...entry.models] } : {}),
...(entry.liveModels !== undefined ? { liveModels: entry.liveModels } : {}),
Expand Down Expand Up @@ -251,6 +255,7 @@ export function enrichProviderFromRegistry(name: string, prov: OcxProviderConfig
if (prov.keyOptional === undefined && seed.keyOptional !== undefined) prov.keyOptional = seed.keyOptional;
if (prov.freeTier === undefined && seed.freeTier !== undefined) prov.freeTier = seed.freeTier;
if (prov.modelSuffixBracketStrip === undefined && seed.modelSuffixBracketStrip !== undefined) prov.modelSuffixBracketStrip = seed.modelSuffixBracketStrip;
if (prov.responsesPath === undefined && seed.responsesPath !== undefined) prov.responsesPath = seed.responsesPath;
if (!prov.headers && seed.headers) prov.headers = { ...seed.headers };
}

Expand Down Expand Up @@ -281,6 +286,7 @@ function entryToPreset(entry: ProviderRegistryEntry): DerivedProviderPreset {
label: entry.label,
adapter: entry.adapter,
baseUrl: entry.baseUrl,
...(entry.responsesPath ? { responsesPath: entry.responsesPath } : {}),
auth: entry.authKind === "forward" ? "forward" : entry.authKind === "oauth" ? "oauth" : entry.authKind === "local" ? "local" : "key",
...(entry.codexAccountMode ? { codexAccountMode: entry.codexAccountMode } : {}),
...(entry.codexAccountMode ? { provider: providerConfigSeed(entry) } : {}),
Expand Down
Loading
Loading