Skip to content

Fix resolution layer and cleanup issues - #131

Merged
qwrobins merged 2 commits into
mainfrom
codex/pr4-resolution-cleanup
Jun 10, 2026
Merged

Fix resolution layer and cleanup issues#131
qwrobins merged 2 commits into
mainfrom
codex/pr4-resolution-cleanup

Conversation

@qwrobins

@qwrobins qwrobins commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes #116
Fixes #117
Fixes #121
Fixes #122
Fixes #123
Fixes #125
Fixes #126

  • Makes friendly-name resolution case-insensitive across teams, users, labels, workflow states, and projects; issue list --state now uses workflow state resolution when a team scope is available, and independent issue-list resolutions run concurrently.
  • Uses server-side containsIgnoreCase project filtering before exact/prefix/substring disambiguation. The optional disk cache from Performance: resolveProjectId downloads the entire workspace project list (twice on scoped miss); resolutions run sequentially; no caching #117 is intentionally skipped because the server-side filter removes the main performance issue without adding cache invalidation complexity.
  • Documents and reports cycle delete as an archive alias, including dry-run and JSON output.
  • Preserves whitespace in gql --var values and defaults bounded autopagination to max page size when no --page-size is set.
  • Hardens file upload/download redirects with manual same-host redirect validation, trims raw OAuth token error bodies, and tolerates concurrent OAuth refresh rotation.
  • Bumps package version to 0.7.1 and updates CHANGELOG, docs, and the bundled linearctl skill.

Breaking changes

None.

Verification

  • bun run typecheck
  • bun run test
  • bun run build
  • bun run build:binary

Summary by CodeRabbit

  • Changed

    • Case-insensitive matching for state, team, user, label, and project resolution
    • Autopagination defaults adjusted for --max/--all requests
    • CLI behavior: cycle delete is presented as an alias of archive in outputs
    • Version bumped to 0.7.1
  • Bug Fixes

    • File upload/download follow redirects only to the same host
    • OAuth refresh tolerates concurrent CLI runs and omits raw response bodies from errors
    • GraphQL --var preserves whitespace
  • Documentation

    • Command docs and changelog updated with the above clarifications

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3d6d7b28-5fad-4785-b87c-89fb505217ad

📥 Commits

Reviewing files that changed from the base of the PR and between a13bd3c and ae63b04.

📒 Files selected for processing (4)
  • src/commands/file.ts
  • src/core/auth/runtime.ts
  • tests/commands/file.test.ts
  • tests/core/auth/runtime.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/core/auth/runtime.ts
  • src/commands/file.ts
  • tests/commands/file.test.ts

📝 Walkthrough

Walkthrough

Version 0.7.1 release addressing seven linked issues: case-insensitive entity resolution across all types; OAuth concurrent refresh recovery and cleaner error messages; file upload/download redirect host validation; cycle delete messaging; autopagination defaults; GraphQL variable whitespace preservation; and parallel issue filter resolution.

Changes

linearctl v0.7.1 Release

Layer / File(s) Summary
Release metadata and documentation
package.json, CHANGELOG.md, docs/commands.md, skills/linearctl/SKILL.md
Version bumped from 0.7.0 to 0.7.1; changelog entry and curated docs clarify case-insensitive filtering, cycle delete aliasing, file redirect host validation, OAuth concurrent-refresh tolerance, and other behavior notes.
Case-insensitive entity resolution and label workspace scoping
src/core/resolution/resolve.ts, tests/core/resolution/resolve.test.ts
Team, user, label, state, and project resolution now use case-insensitive operators; label scoping when team-scoped includes workspace labels (team: null); state resolution requests first: 250; project searches use containsIgnoreCase and include accessibleTeams when scoped.
Autopagination default page size based on --max/--all flags
src/core/pagination/pagination.ts, tests/core/pagination/pagination.test.ts
buildPaginationVariables defaults to MAX_PAGE_SIZE when --max or --all is used without explicit pageSize; added test for capping behavior.
Issue filter construction refactored to parallel promise-based resolution
src/commands/issue.ts, tests/commands/issue.test.ts, tests/commands/project.test.ts
buildIssueFilter resolves state/status, assignee, label, and project concurrently via Promise.all and merges resulting fragments; state filtering supports resolved workflow-state IDs and case-insensitive matches; project filtering respects accessibleTeams.
GraphQL --var value whitespace preservation
src/commands/gql.ts, tests/commands/gql.test.ts
resolveVariables no longer trims the value part of --var key=value; raw substring after = is preserved and passed to parsing, keeping intentional leading/trailing whitespace.
File upload/download redirect validation with host checking
src/commands/file.ts, tests/commands/file.test.ts
Introduces manual redirect: "manual" handling utilities, follows up to 5 redirects, enforces http/https and same-host redirects, and updates upload/download call sites to use validated-follow wrapper; tests cover same-host following and cross-host rejection.
OAuth error message cleanup and concurrent refresh race recovery
src/core/auth/oauth.ts, src/core/auth/runtime.ts, tests/core/auth/oauth.test.ts, tests/core/auth/runtime.test.ts
OAuth error responses are parsed to extract error codes and format messages without embedding raw bodies. Adds oauthCredentialsNeedRefresh and a concurrent-refresh recovery path that reloads on-disk credentials and retries refresh with latest tokens when appropriate; tests verify recovery and failure modes.
Cycle delete implemented as archive alias with user-facing messaging
src/commands/cycle.ts, tests/commands/cycle.test.ts
cycle delete is routed to handleCycleArchive with subcommand context; dry-run reports requestedAction: "delete" and performedAction: "archive" with explanatory note; non-dry-run prints a message that deletion is not supported and the cycle was archived.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

codex

🐰 I hopped through the changelog, tidy and spry,
Kept spaces in vars and redirects nearby.
Cycles now say "archived" with a helpful note,
OAuth recovers when tokens misquote —
v0.7.1, a carrot-flavored patch in the sky!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Fix resolution layer and cleanup issues' is vague and generic, using non-descriptive terms that don't convey specific information about the changeset. Provide a more specific title that highlights the main changes, such as 'Make name resolution case-insensitive and fix file/auth/pagination issues' or similar.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The pull request successfully addresses all seven linked issues (#116, #117, #121, #122, #123, #125, #126) with appropriate code changes across resolution, cycle commands, file handling, gql parsing, OAuth auth, and pagination.
Out of Scope Changes check ✅ Passed All changes are within scope of the linked issues; no unrelated modifications detected. Version bump and documentation updates are standard release practices.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/pr4-resolution-cleanup

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
tests/commands/file.test.ts (1)

381-414: ⚡ Quick win

Add a boundary test for “exactly 5 redirects then success.”

The new redirect tests are good, but they don’t assert the max-redirect boundary. A same-host chain of 5 redirects followed by 200 would prevent regressions on the intended limit.

🤖 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 `@tests/commands/file.test.ts` around lines 381 - 414, Add a new test case in
the same file that exercises handleFileCommand for a same-host chain of exactly
five 302 redirects followed by a 200 response: mock fetchImpl to sequentially
return five 302 responses with Location headers on the same host (e.g.,
"https://uploads.linear.app/stepN") and then a final 200 Response, call
handleFileCommand(["download", "https://uploads.linear.app/some-file.png"], {
...baseOptions(paths), fetchImpl, output: outputPath }), and assert a success
exit code (0), that fetchImpl was called six times, that each request init used
redirect: "manual" and preserved the authorization header ("lin_api_work") on
redirects; keep the same captureOutput setup and cleanup as existing tests and
mirror expectations (fetch call count, headers, redirect handling) to verify the
max-redirect boundary is honored.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@src/commands/file.ts`:
- Around line 86-109: The redirect loop is off-by-one and currently prevents
following the documented 5 redirects; update the loop condition so it allows up
to 5 redirects before failing (e.g., change for (let redirectCount = 0;
redirectCount < 5; redirectCount++) to for (let redirectCount = 0; redirectCount
<= 5; redirectCount++) or otherwise use a MAX_REDIRECTS constant and compare
with <=), keeping the existing logic around fetchImpl(currentUrl,..),
isRedirectStatus(response.status), resolveRedirectUrl(...), and currentUrl
assignment intact so a final non-redirect response can be fetched after the
allowed 5 redirects.

In `@src/core/auth/runtime.ts`:
- Around line 128-172: In recoverConcurrentOAuthRefresh wrap the
refreshAccessToken(...) invocation in a try/catch: call refreshAccessToken
inside try, and on catching an OAuthTokenError (or the specific token error type
thrown by refreshAccessToken) return undefined to mirror the original
invalid_grant fallback; for other errors rethrow or convert to
ProfileResolutionError if you prefer consistent error types. Ensure the catch
prevents building refreshedCredentials, updating the store
(setCredentialsProfile/writeCredentialsFile) or returning a mutated profile when
the refresh itself failed.

---

Nitpick comments:
In `@tests/commands/file.test.ts`:
- Around line 381-414: Add a new test case in the same file that exercises
handleFileCommand for a same-host chain of exactly five 302 redirects followed
by a 200 response: mock fetchImpl to sequentially return five 302 responses with
Location headers on the same host (e.g., "https://uploads.linear.app/stepN") and
then a final 200 Response, call handleFileCommand(["download",
"https://uploads.linear.app/some-file.png"], { ...baseOptions(paths), fetchImpl,
output: outputPath }), and assert a success exit code (0), that fetchImpl was
called six times, that each request init used redirect: "manual" and preserved
the authorization header ("lin_api_work") on redirects; keep the same
captureOutput setup and cleanup as existing tests and mirror expectations (fetch
call count, headers, redirect handling) to verify the max-redirect boundary is
honored.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 01d512eb-ebd0-4cfb-be5e-9c07879dafb8

📥 Commits

Reviewing files that changed from the base of the PR and between eb1fa7e and a13bd3c.

⛔ Files ignored due to path filters (1)
  • src/generated/embedded-skills.ts is excluded by !**/generated/**
📒 Files selected for processing (21)
  • CHANGELOG.md
  • docs/commands.md
  • package.json
  • skills/linearctl/SKILL.md
  • src/commands/cycle.ts
  • src/commands/file.ts
  • src/commands/gql.ts
  • src/commands/issue.ts
  • src/core/auth/oauth.ts
  • src/core/auth/runtime.ts
  • src/core/pagination/pagination.ts
  • src/core/resolution/resolve.ts
  • tests/commands/cycle.test.ts
  • tests/commands/file.test.ts
  • tests/commands/gql.test.ts
  • tests/commands/issue.test.ts
  • tests/commands/project.test.ts
  • tests/core/auth/oauth.test.ts
  • tests/core/auth/runtime.test.ts
  • tests/core/pagination/pagination.test.ts
  • tests/core/resolution/resolve.test.ts

Comment thread src/commands/file.ts Outdated
Comment thread src/core/auth/runtime.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment